目录
原来的集合类, 大部分都不是线程安全的.
Vector, Stack, HashTable, 是线程安全的(不建议⽤), 其他的集合类不是线程安全的.
那如何在多线程环境下安全的使用 集合类呢?
🌴多线程环境使用 ArrayList
1. ⾃⼰使⽤同步机制 (synchronized 或者 ReentrantLock)
前⾯做过很多相关的讨论了. 此处不再展开.可以查看博主之前的博客
2. Collections.synchronizedList(new ArrayList);
3. 使⽤ CopyOnWriteArrayList
CopyOnWrite容器即写时复制的容器。
• 当我们往⼀个容器添加元素的时候,不直接往当前容器添加,⽽是先将当前容器进⾏Copy,复制
出⼀个新的容器,然后新的容器⾥添加元素,
• 添加完元素之后,再将原容器的引⽤指向新的容器。
这样做的好处是我们可以对CopyOnWrite容器进⾏并发的读,⽽不需要加锁,因为当前容器不会添
加任何元素。
所以CopyOnWrite容器也是⼀种读写分离的思想,读和写不同的容器。
优点:
在读多写少的场景下, 性能很⾼, 不需要加锁竞争.
缺点:
- 占⽤内存较多.
- 新写的数据不能被第⼀时间读取到
🎍多线程环境使⽤队列
- ArrayBlockingQueue
- LinkedBlockingQueue
- PriorityBlockingQueue
- TransferQueue
🍀多线程环境使⽤哈希表
HashMap 本⾝不是线程安全的.
在多线程环境下使⽤哈希表可以使⽤:
• Hashtable
•ConcurrentHashMap
🌸 Hashtable
只是简单的把关键⽅法加上了 synchronized 关键字
这相当于直接针对 Hashtable 对象本⾝加锁.
• 如果多线程访问同⼀个 Hashtable 就会直接造成锁冲突.
• size 属性也是通过 synchronized 来控制同步, 也是⽐较慢的.
• ⼀旦触发扩容, 就由该线程完成整个扩容过程. 这个过程会涉及到⼤量的元素拷⻉, 效率会⾮常低.
🌸ConcurrentHashMap
相⽐于 Hashtable 做出了⼀系列的改进和优化. 以 Java1.8 为例
- 读操作没有加锁(但是使⽤了 volatile 保证从内存读取结果), 只对写操作进⾏加锁. 加锁的⽅式仍然是⽤ synchronized, 但是不是锁整个对象, ⽽是 "锁桶" (⽤每个链表的头结点作为锁对象), ⼤⼤降低了锁冲突的概率.
- 充分利⽤ CAS 特性. ⽐如 size 属性通过 CAS 来更新. 避免出现重量级锁的情况.
- 优化了扩容⽅式: 化整为零
- 发现需要扩容的线程, 只需要创建⼀个新的数组, 同时只搬⼏个元素过去.
- 扩容期间, 新⽼数组同时存在.
- 后续每个来操作 ConcurrentHashMap 的线程, 都会参与搬家的过程. 每个操作负责搬运⼀⼩部
分元素. - 搬完最后⼀个元素再把⽼数组删掉.
- 这个期间, 插⼊只往新数组加.
- 这个期间, 查找需要同时查新数组和⽼数组
ConcurrentHashMap每个哈希桶都有一把锁,只有两个线程访问的恰好是同一个哈希桶上的数据才会出现锁冲突。
⭕相关面试题
- ConcurrentHashMap的读是否要加锁,为什么?
- 介绍下 ConcurrentHashMap的锁分段技术?
- ConcurrentHashMap在jdk1.8做了哪些优化?
- Hashtable和HashMap、ConcurrentHashMap 之间的区别?
🔥其他常⻅问题
- 谈谈 volatile关键字的⽤法?
- Java多线程是如何实现数据共享的?
- Java创建线程池的接⼝是什么?参数 LinkedBlockingQueue 的作⽤是什么?
- Java线程共有⼏种状态?状态之间怎么切换的?
- 在多线程下,如果对⼀个数进⾏叠加,该怎么做?
- Servlet是否是线程安全的?
- Thread和Runnable的区别和联系?
- 多次start⼀个线程会怎么样
- 有synchronized两个⽅法,两个线程分别同时⽤这个⽅法,请问会发⽣什么?
- 进程和线程的区别?