并发工具-JUC线程安全集合类
- 遗留的线程安全集合,如
HashTable
(map实现),Vector
(list实现),出现在早期,方法都用synchronized修饰 - 使用Collections装饰的线程安全集合,它们使用装饰器模式,将传入的集合作为内部属性,方法中使用synchronized保证线程安全,如
Collections.synchronizedList
-
Collections.synchronizedMap
等
- JUC安全集合,以关键字分为三类
-
Blocking
类,大部分实现基于锁(ReentrantLock),并提供阻塞的方法 -
CopyOnWrite
类,采用拷贝的方式,修改开销较大 -
Concurrent
类- 内部很多操作使用CAS优化
- 弱一致性
- 遍历时弱一致性,例如,当利用迭代器遍历时,如果容器发生修改,迭代器仍然可以继续进行遍历,但是,内容是旧的
- 大小的弱一致性,
size
操作未必是准确的 - 读取弱一致性
-
在使用
iterator
遍历的同时,还使用add
和remove
修改集合,会因为fail-fast机制让遍历失败,抛出ConcurrentModificationException
1. ConcurrentHashMap
ConcurrentHashMap是线程安全的。这里的线程安全,指的是它的每个操作是线程安全,多个操作的组合并不能保证原子性,也就不是线程安全的了,例如,get
后,进行计算,再put
,这个过程并不能保证线程安全。
对于多步操作,ConcurrentHashMap提供了原子性的方法,如
public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
关于HashMap并发死链,
- HashMap的结构是数组+链表
- HashMap采用拉链法解决hash冲突
- 在JDK 8之前,在解决冲突的链表上添加Entry,是放在链表头,而在JDK 8中是放在链表尾
- 扩容,将数组容量扩大为原来的两倍,重新进行hash
- 并发死链在JDK 8之前发生
- JDK 8及以后的HashMap在并发环境下,不会发生并发死链,但是,在扩容时,容易发生数据丢失
ConcurrentHashMap的并发处理,
-
get
方法没有加锁 - 初始化采用CAS
- 数组中添加元素使用的CAS
- 只有在冲突时,对链表头加锁
- 以上是JDK 8中的处理,而在JDK 7中,使用的是segment数组,处理时,对segment数组加锁
2. LinkedBlockingQueue
- BlockingQueue的一个实现
- 用了两把锁和dummy节点
- 用一把锁时,同一时刻,最多只允许有一个线程执行(生产者和消费者二选一)
- 用两把锁,可以允许两个线程同时执行
- 消费者之间仍然是串行
- 生产者之间仍然是串行
线程安全分析,
- 当节点数大于2(包括dummy节点),putLock保证last节点的线程安全,takeLock保证head节点的线程安全,两把锁保证了入队和出队没有竞争
- 当节点数等于2时,即,一个dummy节点,一个正常节点,仍然是两把锁锁两个对象,不会竞争
- 当节点总数等于1时,即,只有dummy节点,这时,take线程会被notEmpty条件阻塞
ArrayBlockingQueue,它是数组的结构,只能使用一把锁,并发度不如LinkedBlockingQueue。
3. ConcurrentLinkedQueue
与LinkedBlockingQueue设计类似,
- 两把锁和dummy节点
- 这把锁采用了CAS