前言
线程安全集合是指该集合可以在多线程并发读取的状态下,能够保持数据集合有序,不发生同步错误。
一、早期的线程安全集合
我们先说早期的线程安全集合,也是各种面试题或者考试题经常喜欢问的,它们是Vector和HashTable。
说实话,在实际的项目开发中,从来没有用过这两个线程集合,已经过时了,不知道为什么一直在考。
1.1 Vector
Vector
和ArrayList
类似,是长度可变的数组,与ArrayList
不同的是,Vector
是线程安全的,它几乎给所有的public
方法都加上了sychronized
关键字。由于加锁倒是性能降低,在不需要并发访问时,这种强制性的同步就显得多余,所以现在几乎没有什么人在使用。
1.2 HashTable
HashTable
和HashMap
类似,不同的是HashTable
是线程安全的,它也是几乎的给所有的public
方法都加上了sychronized
关键字,还有一个不同点是:**HashTable
的K, V
都不能是null
,但是HashMap
可以。**现在HashTable
也是因为性能的问题,几乎没有人使用了。
二、Collections包装方法
由于ArrayList
和HashMap
性能好,但是不是线程安全,所以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
ConcurrentHashMap
和HashTable
都是线程安全的集合,它们的不同主要是加锁粒度上的不同。HashTable
的加锁方法是给每个方法加上synchronized
关键字,这样锁的是整个对象。而ConcurrentHashMap
是有更细粒度的锁。在JDK1.8之前,ConcurrentHashMap
加的是分段锁,即Segment
,每个Segment
含有整个table
的一部分,这样不同分段之间的并发操作就互不影响。
JDK1.8之后对此进行了进一步的改进,取消了Segment
,直接在table
元素上加锁,实现对每一行加锁,进一步减小了并发冲突的概率。
3.2 CopyOnWriteArrayList和CopyOnWriteArraySet
针对涉及到数据修改的部分,都会使用ReentrantLock
锁住操作,并将修改或添加的元素,通过拷贝的方式,加入数组中,最后修改数组的引用为新复制的数组。读取时直接读取数组,不需要获取锁。
3.3 其他
初次之外还有ConcurrentSkipListMap
、ConcurrentSkipListSet
、ConcurrentLinkedQueue
、ConcurrentLinkedDeque
等,可以在java.util.concurrent
下查看其他的线程安全的数据结构。至于为什么没有ConcurrentArrayList
,原因是无法设计一个通用的而且可以规避ArrayList
的并发瓶颈的线程安全的集合类,只能锁住整个list,这个用Collections
里的包装类就能办到。