16.9.3 线程安全的集合类

16.9.3 线程安全的集合类

实际上从Java5开始,在java.util.concurrent包下提供了大量支持高效并发访问的集合接口和实现类,如图16.7所示。
这里有一张图片

线程安全集合分类

从图16.17所示的类图可以看出,这些线程安全的集合类可分为如下两类

  • Concurrent开头的集合类,如ConcurrentHashMapConcurrentSkipListMapConcurrentSkipListSetConcurrentLinkedQueue,ConcurrentLinkedDeque.
  • CopyOnWrite开头的集合类,如CopyOnWriteArrayListCopyOnWriteArraySet

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和普通集合不同的是,因为ConcurrentLinkedQueueConcurrentHashMap支持多线程并发访问,所以当使用迭代器来遍历集合元素时,该迭代器可能无法反映出创建迭代器之后所做的修改,并且程序不会抛出任何异常

java8对ConcurrentHashMap的扩展

Java8扩展了ConcurrentHashMap的功能,Java8为该类新增了30多个新方法,这些方法可借助于StreamLambda表达式支持执行聚集操作。

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适合用在读取操作远远大于写入操作的场景中,例如缓存等