Java基础: 线程安全的集合类

一.包装线程不安全的集合

在集合中学到的ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap等都是线程不安全的,也就是说,当多个并发线程向这些集合中存、取元素时,就可能会破坏这些集合的数据完整性。

如果程序中有多个线程可能访问以上这些集合,就可以使用Collections提供的类方法把这些集合包装成线程安全的集合。Collections提供了如下静态方法。

在这里插入图片描述比如,如果想在多线程中使用线程安全的HashMap,则可以使用以下代码:

 使用Collections的synchronizedMap方法将一个普通的
 HashMap map = (HashMap) Collections.synchronizedMap(new HashMap<>());

注意:
如果需要把某个集合包装成线程安全的集合,则应该在创建之后立即包装,如上程序所示—当HashMap对象创建后立即包装成线程安全的HashMap对象.

二.线程安全的集合类

从Java5开始,在java.util.concurrent包下提供了大量支持高效并发访问的集合接口和实现类,如下图所示:
在这里插入图片描述从上图所示的类图可以看出,这些线程安全的集合类分为两大类:

(1)以Concurrent开头的集合类: 如 ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentLinkedDeque、ConcurrentSkipListMap和ConcurrentSkipListSet .

(2)以CopyOnWrite开头的集合类:如 CopyOnWriteArrayList、CopyOnWriteArraySet .

其中以Concurrent开头的集合类代表了支持并发访问的集合,它们可以支持多个线程并发写入访问,这些写入线程的所有操作都是线程安全的,但读取操作不必锁定。以Concurrent开头的集合类采用了更复杂的算法来保证永远不会锁住整个集合,因此在并发写入时有较好的性能。

当多个线程共享访问一个公共集合时,ConcurrentLinkedQueue是一个恰当的选择。ConcurrentLinkedQueue不允许使用null元素。ConcurrentLinkedQueue实现了多线程的高效访问,多个线程访问ConcurrentLinkedQueue集合时无须等待。

在默认情况下,ConcurrentHashMap支持16个线程并发写入,当有超过16个线程并发向该Map中写入数据时,可能有一些线程需要等待。实际上,程序通过设置concurrencyLevel构造参数(默认值为16)来支持更多的并发写入线程。

与前面介绍的HashMap和普通集合不同的是,因为ConcurrentLinkedQueue和ConcurrentHashMap支持多线程并发访问,所以当使用迭代器来遍历元素时,该迭代器可能不能反映出创建迭代器之后所做的修改,但程序不会抛出任何异常。

Java8拓展了ConcurrentHashMap的功能,为该类新增了30多个新方法,这些方法可借助于Stream和Lambda表达式支持执行聚焦操作。ConcurrentHashMap新增的方法大致可分为如下三类:

在这里插入图片描述除此之外,ConcurrentHashMap还新增了mappingCount()、newKeySet()等方法,增强后的ConcurrentHashMap更适合作为缓存实现类使用。

注意:
使用java.util包下的Collection作为集合对象时,如果该集合对象创建迭代器集合元素发生改变,则会引发ConcurrentModificationException异常.

由于CopyOnWriteArraySet的底层封装了CopyOnWriteArrayList,因此它的实现机制完全类似于CopyOnWriteList集合。

对应CopyOnWriteArrayList集合,正如它的名字所暗示的,它采用复制底层数组的方式来实现写操作。

当线程对CopyOnWriteArrayList集合执行读取操作时,线程将会直接读取集合本身,无须加锁与阻塞。当线程对CopyOnWriteArrayList集合执行写入操作时(包括调用add()、remove()、set()等方法),该集合会在底层复制一份新的数组,接下来对新的数组执行写入操作。由于对CopyOnWriteArrayList集合的写入操作都是对数组的副本执行操作,因此它是线程安全的。

需要指出的是,由于CopyOnWriteArrayList执行写入操作时需要频繁地复制数组,性能比较差,但由于读操作与写操作不是操作同一个数组,并且读操作也不需要加锁,因此读操作就很快、很安全。由此可见,CopyOnWriteArrayList适合用在读取操作远远大于写入操作的场景中,比如缓存等。

猜你喜欢

转载自blog.csdn.net/gaolh89/article/details/104852565