⭐ 作者:小胡_不糊涂
作者主页:小胡_不糊涂的个人主页
收录专栏:JavaEE
持续更文,关注博主少走弯路,谢谢大家支持
ConcurrentHashMap
- 1. Hashtable
- 2. ConcurrentHashMap
1. Hashtable
Hashtable保证线程安全,主要就是给关键方法加上synchronized,相当于直接针对Hashtable 对象本⾝加锁。
例如:
public synchronized Type put(Type key,Type value){}
public synchronized Type get(Type key,Type value){}
- 如果多线程访问同⼀个 Hashtable 就会直接造成锁冲突
- size 属性也是通过 synchronized 来控制同步,也是⽐较慢的
- ⼀旦触发扩容,就由该线程完成整个扩容过程。这个过程会涉及到⼤量的元素拷贝,效率会⾮常低
2. ConcurrentHashMap
- ConcurrentHashMap最核心的改进就是把一个全局的大锁,改成了每个链表独立的一把小锁。就是把每个链表的头结点作为锁对象(synchronized可以使用任意对象作为锁对象)。大幅度降低了锁冲突的概率。
- 充分利用CAS特性,把一些不必要加锁的环节省略加锁。
比如:size属性就通过CAS来更新,避免出现重量级锁的情况。
- 读操作没有加锁(但是使⽤了 volatile 保证从内存读取结果),只对写操作进⾏加锁。加锁的⽅式仍然是是⽤ synchronized,但是不是锁整个对象,⽽是 “锁桶” (⽤每个链表的头结点作为锁对象),⼤⼤降低了锁冲突的概率。
使得读和读、读和写之间都不会有锁竞争。
ConcurrentHashMap的底层编码,在修改的时候会避免使用++ --这种非原子的操作。而使用 = 进行修改(原子的)。
读的时候,要么读到的是写之前的旧值,要么是读到写之后的新值,不会出现读到一个一半的值。
- 优化了扩容⽅式: 化整为零,多次搬运
发现需要扩容的线程,只需要创建⼀个新的数组,同时只搬⼏个元素过去。
扩容期间,新⽼数组同时存在。后续每个来操作 ConcurrentHashMap 的线程,都会参与搬家的过程,每个操作负责搬运⼀⼩部分元素。搬完最后⼀个元素再把⽼数组删掉。
这个期间,插⼊只往新数组加。但查找需要同时查新数组和⽼数组
Hashtable和HashMap都实现了Map接口,但是Hashtable的实现是基于Dictionary抽象类的。ConcurrentHashMap是HashTable的替代,比HashTable的扩展性更好。