目录
一、线程安全的集合类
1.1、多线程环境下使用ArrayList
1.2、多线程使用队列
1.3、多线程环境使用哈希表
1.3.1、Hashtable
1.3.2、ConcurrentHashMap
一、线程安全的集合类
在多线程的环境下,多个线程对同一个共享变量进行写操作的时候,有可能出现线程安全问题。
原来的集合类,大部分都是线程不安全的。
Vetor,Stack,HashStack,是线程安全的但是已经不推荐使用了,其它集合类不是线程安全的。
1.1、多线程环境下使用ArrayList
(1)、自己手动加锁(synchronized或ReentrantLock)
这里不太理解的,可以看之前的博客详解这块的内容
(2)、collections.synchronizedList(new ArrayList)
(3)、使用JUC包中提供的集合类(CopyOnWrite)
CopyOnWrite写时拷贝技术(多线程环境下强烈推荐使用JUC包下的类)
- 当我们往一个容器中添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy复制出一个新的容器,然后在新的容器中添加元素。
- 添加完元素之后,再将原容器的引用指向新的容器。
这样做的好处就是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。
优点:
在读多写少的环境下,大量运用了CAS操作,性能很高(纯用户态操作),不需要进行加锁。
缺点:
- 占用内存较多,因为每次写的时候都要去创建一个副本,对内存空间消耗比较大。
- 新写的数据不能被第一时间读取到。
1.2、多线程使用队列
1、ArrayBlockingQueue
基于数组实现的阻塞队列
2、LinkedBlockingQueue
基于链表实现的阻塞队列
3、PriorityBlockingQueue
基于堆实现的带优先级的阻塞队列
4、TransferQueue
最多只包含一个元素的阻塞队列
1.3、多线程环境使用哈希表
HashMap本身不是线程安全的,在多线程环境下使用哈希表可以使用:
- HashTable
- ConcurrentHashMap
1.3.1、Hashtable
- 如果多线程访问同一个Hashtable就会直接造成锁冲突。
- size属性也是通过synchronized来控制同步的,也是比较慢的。
- 一旦触发扩容,就由该线程完成整个扩容过程,这个过程会涉及到大量的元素拷贝,效率非常低。(所以强烈不推荐使用)
HashMap和Hashtable是如何组织数据的?底层的数据结构是什么?
*HashMap实现原理
*Hashtable实现原理
1.3.2、ConcurrentHashMap
相比于Hashtable做了一系列优化和改进。(更推荐使用)
- 读操作没有加锁(但是使用了volatile来保证内存可见性),只对写操作进行加锁,加锁的方式仍然是用synchronized,但是不是锁整个对象,而是锁“桶” (用每个链表的头节点作为锁对象),大大降低了锁冲突的概率.
- 充分利用了CAS特性,比如size属性通过CAS来更新,避免出现重量级锁的情况.
- 优化了扩容方式:化整为零
*发现需要扩容的线程,只需要创建一个新的数组,同时只搬去几个元素过去。 *扩容期间,新老数组同时在。 *后续每个来操作ConcurretnHashMap的线程,都会参与搬家过程,每个操作负责搬运一小部分元素。 *搬完最后一个元素再把老数组删除。 *这个期间,插入只往新数组中添加。 *这个期间,查找需要同时查找新老数组元素。
ConcurrentHashMap每个哈希桶都有一把锁,只有两个线程去访问的恰好是同一个哈希桶上的数据才会发生锁冲突。