|
JavaEE
JavaEE——CAS
JavaEE——JUC
标准库里大部分的集合类, 都是线程不安全的.
少数几个安全的: Vector, Stack, HashTable (不太推荐使用)
自己使用同步机制 (synchronized
或者 ReentrantLock
)
如果需要在多线程环境下保证集合类的线程安全, 最简单的做法, 就是自己加锁Collections.synchronizedList(new ArrayList);
就是在外面套上一层壳, 壳上加锁了.
使用 CopyOnWriteArrayList (不加锁保证线程安全)
CopyOnWrite, 写(修改)时拷贝(复制)
- 当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素.
- 添加完元素之后,再将原容器的引用指向新的容器.
# 注意 #
这个操作适用范围非常有限, 如果元素特别多, 修改的特别频繁, 就不太适合使用这种方式了.
ArrayBlockingQueue (基于数组实现的阻塞队列)
LinkedBlockingQueue (基于链表实现的阻塞队列)
PriorityBlockingQueue (基于堆实现的带优先级的阻塞队列)
TransferQueue (最多只包含一个元素的阻塞队列)
在多线程环境下使用哈希表可以使用
ConcurrentHashMap
(推荐)HashTable
Hashtable 属于无脑给各种方法加 synchronized
, 相当于给 this
加锁.
相当于是针对哈希表对象来加锁, 一个哈希表, 只有一个锁.
如果有多个线程, 无论都是如何操作这个哈希表, 都会产生锁冲突.
# 注意 #
产生锁冲突, 对性能影响是非常大的.
HashTable 扩容:
当 put 元素的时候, 发现当前的负载因子已经超过阙值, 就需要触发扩容, 申请一个更大的数组, 然后把之前的旧数据给搬到新的数组上.
# 注意 #
但这里有一个很大的问题:如果元素个数特别多, 搬运操作的开销就会很大. 执行一个 put 操作, 正常瞬间完成 O(1). 但是触发扩容的这一下 put, 就可能会卡很久.
ConcurrentHashMap
加锁的方式仍然是是用 synchronized
, 但是不是锁整个对象, 而是 “锁桶”(哈希桶) (用每个链表的头结点作为锁对象), 大大降低了锁冲突的概率.
读操作没有加锁(但是使用了 volatile
保证从内存读取结果), 只对写操作进行加锁.
充分利用 CAS
特性. 比如像维护元素个数, 都是通过 CAS 来更新. 避免出现重量级锁的情况.
对于扩容操作, 进行了特殊的优化: “化整为零”
ConcurrentHashMap 在扩容的时候, 就不再是一次性完成搬运了,
- 在扩容过程中, 旧的和新的会同时存在一段时间, 每次进行哈希表的操作, 都会把旧的内存上的元素搬运一部分到新的空间上
- 在这个过程中, 如果要查询元素, 旧的和新的一起查
- 如果要插入元素, 直接往新的上插入
- 如果是删除元素, 直接删了不用搬运了
小知识:
- HashMap key 允许为 null
- HashTable 和 ConurrentHashMap key 不能为 null
旧版本的 ConcurrentHashMap 的实现方式
注 :
从 Java 8 开始, 就没有了.
好几个链表公用同一把锁, (锁冲突概率要比每个链表一把锁更高, 代码实现起来更复杂了)
|
以上就是今天要讲的内容了,希望对大家有所帮助,如果有问题欢迎评论指出,会积极改正!!