Java中提供了许多集合类,其中有的是线程安全的,有的是线程不安全的。线程安全的集合类有:
1. Vector:Vector类实现了一个动态数组,与ArrayList相似,但Vector是同步访问的
2. Stack:Stack是Vector的一个子类,实现了一个“后进先出”的栈
3. Hashtable:Hashtable是一个散列表,与HashMap类似
Hashtable在关键方法上加上了synchronized,相当于对this加锁(整张表都加上锁),则Hashtable只有一把锁,即使是修改或读取不同链表上的元素,也会触发锁冲突
通过上图我们可以发现:读取数据时不涉及到线程安全问题,修改两个不同链表上的元素时,也不会涉及线程安全问题,而当修改的是同一链表上的元素时,则可能会涉及到线程安全问题。
因此,针对读取操作,无需加锁,不同链表的操作,也无需加锁,而当针对同一链表操作时,需要加锁,此时,我们可以考虑使用ConcurrentHashMap
4. ConcurrentHashMap:ConcurrentHashMap能够做到读数据不加锁,且在进行写操作时锁的粒度更小,可以允许多个修改操作并发进行,Java 1.7及其之前,ConcurrentHashMap是通过“分段锁”来实现的,即给若干链表分配一把锁,然而这种方法需要引入额外的空间开销,且实现更复杂
因此,从Java 8 开始,就变成了每个链表一把锁了
此时,若不是操作同一个链表的锁,就不会发生锁冲突
然而此种方法是否需要更多的空间?
不会产生更多的空间代价。Java中任意一个对象都可以直接作为锁对象。哈希表中本就需要有数组,数组的元素都是已经存在的(每个链表的头节点),此时,只需使用链表的头节点(数组元素)作为锁对象即可
且ConcurrentHashMap针对扩容也进行了优化。
Hashtable一旦触发扩容,就由该线程完成整个扩容操作,此时可能会涉及到大量的元素拷贝,因此效率会较低,耗时较长
而ConcurrentHashMap则是采用“化整为零”的方法,即当发现需要扩容时,创建一个新的数组,同时搬运几个元素过去,后续每次线程操作ConcurrentHashMap时,都会搬运元素,每次操作搬运一部分元素。在此扩容期间,新旧数组同时存在。当搬运完最后一个元素时,再把旧数组删掉。在此期间内,插入数据只会向新数组中插入,而查找需要同时查询新数组和旧数组
5. synchronizedList:synchronizedList是标准库中提供的一个基于sychronized进行线程同步的list。synchronizedList的关键操作上都带有synchronized
6. CopyOnWriteArrayList:CopyOnWrite即写时拷贝,当读取顺序表时,此时无线程安全问题,而当有线程要修改其中的值时,就会将list复制一份,修改新表中的内容,并修改引用的指向(操作是原子的,无需加锁)
然而因此此种操作需要进行复制,因此修改操作不能太频繁且表也不应太大
除此之外,也可以根据需求,使用synchronized或ReentrantLock自己实现线程安全的ArrayList
7. BlockingQueue(阻塞队列):
ArrayBlockingQueue:基于数组实现的阻塞队列
LinkedBlockingQueue:基于链表实现的阻塞队列
PriorityBlockingQueue:基于堆实现的带有优先级的阻塞队列
...
虽然以上这些类都是线程安全的,但也不一定能够满足我们多线程操作的需求,因此我们需要根据实际情况选择使用或自己加以实现