目录
Ⅱ.ConcurrentHashMap只是给写操作加锁,读操作不加锁
一.关于三者的线程安全的问题
HashMap是线程不安全的,而HashTable和ConcurrentHashMap这两者是线程安全的
二.ConcurrentHashMap的优化策略
在多线程的情况下,要保证线程安全的情况下,不推荐使用HashTable,因为它是无脑的给各种方法加synchronized,对于资源的消耗比较大,性能较低
现在推荐使用的是ConcurrentHashMap,它有四个很重要的优化策略
Ⅰ.锁粒度的控制
HashTable是直接在方法外面加上synchronized,相当于是对this进行加锁,那就是相当于对哈希表对象来加锁
一个哈希表只有一个锁, 在多线程里面,无论这些线程是如何操作,这个哈希表都会产生锁冲突
而ConcurrentHashMap是每个哈希桶都有自己的锁,两个线程同时访问同一个哈希桶的时候才有锁冲突,如果不是同一个哈希桶,就没有锁冲突了,哈希桶的个数多了就大大降低了产生锁冲突的概率,性能就大大提升了
而旧版本的ConcurrentHashMap使用的是分段锁,分段锁是好几个链表共用一把锁,发生锁冲突的概率比每个链表一把锁更高,代码实现起来更复杂
Ⅱ.ConcurrentHashMap只是给写操作加锁,读操作不加锁
两个线程同时修改,才会产生锁冲突
两个线程同时读,不会产生锁冲突
但是一个线程读的同时,另一个线程修改,因为读操作不加锁,所以不涉及锁冲突,但是这个操作可能有线程安全问题,即有可能读到的数据是修改了一半的数据
但是ConcurrentHashMap在设计的时候考虑到了这一点,所以能够保证读到的数据不是修改一半的数据(要么是旧版本的数据,要么是新版本的数据)
读操作中也广泛应用了volatile来保证读到的数据是及时的
Ⅲ.充分利用了CAS的特性
很多地方直接使用CAS实现的轻量级锁来实现的,比如维护元素个数,就是通过CAS而不是加锁
ConcurrentHashMap思路就是能不加就不加
ConcurrentHashMap核心优化思路是尽可能一切,降低锁冲突的概率
四.ConcurrentHashMap的扩容操作
ConcurrentHashMap对于扩容操作,进行了化整为零的操作(有些像写时拷贝)
HashTable的扩容操作:当put元素的时候,发现当前的负载因子已经超过了阈值,就需要触发扩容, 它会申请一个更大的数组,然后把之前旧的数据给搬运到新的数组上
这个操作有一个很大的问题:如果元素的个数足够多,搬运的开销就会很大,本来执行一个put操作的时间复杂度时O(1),但是触发扩容的这次put, 就可能会卡很久的时间
ConcurrentHashMap在扩容的时候,并不是直接一次性搬运完成,而是分为多次,每一次都搬运一点.
扩容过程中,旧的数组和新的数组会同时存在一段时间, 在每次进行哈希表操作的时候,都会把旧的内存上的元素搬运一部分到新的空间上,直到最终搬运完成, 才会释放旧的空间
如果需要查询元素,则会在旧的和新的数组上面一起查
如果需要新增元素,则直接往新的数组上插入
如果需要删除元素,则直接删了不用搬运
这个操作就像是爬楼梯一样,如果你想直接跳上二楼很难,而如果时爬楼梯,每一步都上升一点,积少成多就上去了
另外HashMap的key允许为null,而HashTable和ConcurrentHashMap的key不能为null