16.9.3 线程安全的集合类
16.9.3 线程安全的集合类
实际上从Java5
开始,在java.util.concurrent
包下提供了大量支持高效并发访问的集合接口和实现类,如图16.7所示。
线程安全集合分类
从图16.17所示的类图可以看出,这些线程安全的集合类可分为如下两类
- 以
Concurrent
开头的集合类,如ConcurrentHashMap
、ConcurrentSkipListMap
、ConcurrentSkipListSet
、ConcurrentLinkedQueue
,ConcurrentLinkedDeque
. - 以
CopyOnWrite
开头的集合类,如CopyOnWriteArrayList
、CopyOnWriteArraySet
Concurrent开头的集合类
其中以Concurrent
开头的集合类代表了支持并发访问的集合,它们可以支持多个线程并发写入
访问,这些写入线程的所有操作都是线程安全的,但读取操作不必锁定。以Concurrent
开头的集合类采用了更复杂的算法来保证永远不会锁住整个集合,因此在并发写入时有较好的性能。
ConcurrentLinkedQueue
当多个线程共享访问一个公共集合时,ConcurrentLinkedQueue
是一个恰当的选择。ConcurrentLinkedQueue
不允许使用null
元素。ConcurrentLinkedQueue
实现了多线程的高效访问,多个线程访问ConcurrentLinkedQueue
集合时无须等待。
ConcurrentHashMap
在默认情况下,ConcurrentHashMap
支持16个线程并发写入,当有超过16个线程并发向该Map
中写入数据时,可能有一些线程需要等待。实际上,程序通过设置concurrencyLevel
构造参数(默认值为16)来支持更多的并发写入线程
java.util.concurrent包下的集合和java.util包的集合在迭代时的区别
使用java.util
包下的Collection
作为集合对象时,如果该集合对象创建迭代器后集合元素发生改变,则会引发ConcurrentModificationException
异常。
与前面介绍的HashMap
和普通集合不同的是,因为ConcurrentLinkedQueue
和ConcurrentHashMap
支持多线程并发访问,所以当使用迭代器来遍历集合元素时,该迭代器可能无法反映出创建迭代器之后所做的修改,并且程序不会抛出任何异常。
java8对ConcurrentHashMap的扩展
Java8
扩展了ConcurrentHashMap
的功能,Java8
为该类新增了30多个新方法,这些方法可借助于Stream
和Lambda
表达式支持执行聚集操作。
Java8的ConcurrentHashMap新的的方法
ConcurrentHashMap
新增的方法大致可分为如下三类。
forEach
系列(forEach
,ForEachKey
,ForEachValue
,ForEachEntry
)search
系列(search
,SearchKeys
,SearchValues
,SearchEntries
)reduce
系列(reduce
,reduceToDouble
,reduceToLong
,reduceKeys
,reduceValues
)
除此之外,ConcurrentHashMap
还新增了mappingCount()
、newKeySet()
等方法,增强后的ConcurrentHashMap
更适合作为缓存实现类使用。
CopyOn开头的集合
由于CopyOnWriteArraySet
的底层封装了CopyOnWriteArrayList
,因此它的实现机制完全类似于CopyOnWriteArrayList
集合。
对于CopyOnWriteArrayList
集合,正如它的名字所暗示的,它釆用复制底层数组的方式来实现写操作
- 当线程对
CopyOnWriteArrayList
集合执行读取操作时,线程将会直接读取集合本身,无须加锁与阻塞。 - 当线程对
CopyOnWriteArraylist
集合执行写入操作时(包括调用add()
、remove()
、set()
等方法),该集合会在底层复制一份新的数组,接下来对新的数组执行写入操作。- 由于对**
CopyOnWriteArrayList
集合的写入操作都是对数组的副本执行操作,因此它是线程安全的**
- 由于对**
需要指出的是,由于CopyOnWriteArrayList
执行写入操作时需要频繁地复制数组,性能比较差,但由于读操作与写操作不是操作同一个数组,而且读操作也不需要加锁,因此读操作就很快、很安全。
由此可见,CopyOnWriteArrayList
适合用在读取操作远远大于写入操作的场景中,例如缓存等。