目录
前言
本文章主要介绍在多线程环境下,如何线程安全的使用一些常用的集合类(顺序表和哈希表)。
顺序表
1、自己使用同步锁机制(synchornized和ReentrantLock)保证线程安全
2、Collections.synchronizedList()
3、写时拷贝
Java中提供了CopyOnWrit这个容器来解决线程安全问题。
它的解决思路是:
优点:
缺点:
队列
关于队列的线程安全,自然使用到的是阻塞队列:
1. ArrayBlockingQueue: 基于数组实现的阻塞队列
2. LinkedBlockingQueue: 基于链表实现的阻塞队列
3. PriorityBlockingQueue: 基于堆实现的带优先级的阻塞队列
4. TransferQueue: 最多只包含⼀个元素的阻塞队列
哈希表
HashMap本身是线程不安全的,在多线程环境下可以使用Hashtable/ConcurrentHashMap
1、Hashtable
Hashtable这个类只是对HashMap进行简单的加锁,也就是在关键方法上增加了synchronized关键字:
这就导致只要多个线程同时调用put或者get方法,即使他们想要访问的不是同一组数据,照样会进入阻塞状态,也就是所冲突,极大的影响了运行效率。
另外,如何在某一个线程使用put操作时,如果需要扩容,那么就会由当前线程进行扩容,如果涉及大量的元素拷贝,那么多线程的运行效率又会大幅降低。
总的来讲,HashTable相当于对整个哈希桶上了锁:
2、ConcurrentHashMap[推荐使用]
为了降低锁冲突概率,ConcurrentHashMap做出了以下优化:
- 读操作:没有加锁,但是加了volatile保证内存可见。
- 写操作:对每一个链表都进行了加锁(synchronized实现),如果写入数组中不同的下标,那么就不会发生所冲突,如图:
- CAS的使用:在锁竞争并不激烈的情况下,采用CAS来更新size(键值对 增加/减少),这种方式不用加锁,更轻量,提高运行效率(jdk8引入)。
- 分散计数器槽:在锁竞争激烈时,使用CAS就可能出现忙等的情况,使用CAS修改size就不太好了。为了避免多个线程同时修改同一个数据(size),ConcurrentHashMap引入了分散计数器槽(Counter Cells),它的原理如下:
- 扩容方式:与HashTable让发现需要扩容的线程去完成整个扩容任务不同,Concurrent把扩容任务分散给了多个线程去完成,即“化整为零”。
大致步骤如下:
这种扩容方式大大提高了并发性能(逐步迁移,降低阻塞概率)以及系统稳定性(扩容时,单个线程锁占用时间不会激增)。
完