Bootstrap

Java线程安全的集合

前言

线程安全集合是指该集合可以在多线程并发读取的状态下,能够保持数据集合有序,不发生同步错误。

一、早期的线程安全集合

我们先说早期的线程安全集合,也是各种面试题或者考试题经常喜欢问的,它们是Vector和HashTable。

说实话,在实际的项目开发中,从来没有用过这两个线程集合,已经过时了,不知道为什么一直在考。

1.1 Vector

VectorArrayList类似,是长度可变的数组,与ArrayList不同的是,Vector是线程安全的,它几乎给所有的public方法都加上了sychronized关键字。由于加锁倒是性能降低,在不需要并发访问时,这种强制性的同步就显得多余,所以现在几乎没有什么人在使用。

1.2 HashTable

HashTableHashMap类似,不同的是HashTable是线程安全的,它也是几乎的给所有的public方法都加上了sychronized关键字,还有一个不同点是:**HashTableK, V都不能是null,但是HashMap可以。**现在HashTable也是因为性能的问题,几乎没有人使用了。

二、Collections包装方法

由于ArrayListHashMap性能好,但是不是线程安全,所以Collections工具类中提供了相应的包装方法将他们包装成相应的线程安全的集合:

List<E> synchronizedList = Collections.sychronizedList(new ArrayList<E>());

Set<E> sychronizedSet = Collections.sychronizedSet(new HashSet<E>());

Map<K, V> synchronizedMap = Collections.sychronizedMap(new HashMap<K, V>());

Collections针对每种集合都声明了一个线程安全的包装类,在原集合的基础上添加了锁对象,集合中的每个方法都通过这个锁对象实现同步

三、java.util.concurrent包中的集合

3.1. ConcurrentHashMap

ConcurrentHashMapHashTable都是线程安全的集合,它们的不同主要是加锁粒度上的不同。HashTable的加锁方法是给每个方法加上synchronized关键字,这样锁的是整个对象。而ConcurrentHashMap是有更细粒度的锁。在JDK1.8之前,ConcurrentHashMap加的是分段锁,即Segment,每个Segment含有整个table的一部分,这样不同分段之间的并发操作就互不影响。
JDK1.8之后对此进行了进一步的改进,取消了Segment,直接在table元素上加锁,实现对每一行加锁,进一步减小了并发冲突的概率。

3.2 CopyOnWriteArrayList和CopyOnWriteArraySet

针对涉及到数据修改的部分,都会使用ReentrantLock锁住操作,并将修改或添加的元素,通过拷贝的方式,加入数组中,最后修改数组的引用为新复制的数组。读取时直接读取数组,不需要获取锁。

3.3 其他

初次之外还有ConcurrentSkipListMapConcurrentSkipListSetConcurrentLinkedQueueConcurrentLinkedDeque等,可以在java.util.concurrent下查看其他的线程安全的数据结构。至于为什么没有ConcurrentArrayList,原因是无法设计一个通用的而且可以规避ArrayList的并发瓶颈的线程安全的集合类,只能锁住整个list,这个用Collections里的包装类就能办到。

参考

  1. Java线程安全的集合详解
;