目录
ConcurrentHashMap源码解析
一、什么是ConcurrentHashMap
ConcurrentHashMap与HashMap数据结构一致,他是线程安全的。
ConcurrentHashMap中是不允许key和value为null的,而HashMap是允许的。
ConcurrentHashMap的扩容是可以多线程扩容的,桶中节点数量可以多线程计数。
数据结构详情:HashMap源码解析
jdk1.7和1.8是如何对并发做处理的?
jdk1.7: 使用了一个继承ReentrantLock的分段锁Segment来实现并发的
jdk1.8:是通过CAS + synchronized + volatile 的方式来实现,而且锁粒度也缩减到了桶上。
CAS详情:什么是CAS
synchronized详情:全面理解Synchronized
volatile:全面理解Volatile关键字
二、ConcurrentHashMap锁的颗粒度
jdk1.8锁的颗粒度在桶上:通过数据结构我们可以看到,底层采用的是数组+链表+红黑树(在一定的情况下才会有红黑树)。这个桶就是数组上的那个链表。ConcurrentHashMap并发时加锁是加在这个桶中的第一个node节点,也就是对整个桶进行了加锁。这样的话,若是有2个线程同时对ConcurrentHashMap进行put操作,若是线程1对Node[1]桶里的数据进行更新,线程2对Node[3]桶中的数据进行更新,那么就可以同时进行,不会被阻塞。
三、源码解析
1.构造函数及部分参数
//正在扩容
static final int MOVED = -1; // hash for forwarding nodes
//当前节点是红黑树节点
static final int TREEBIN = -2; // hash for roots of trees
//当前节点是用在computeIfAbsent和compute方法中的占位节点
static final int RESERVED = -3; // hash for transient reservations
//map的数据结构,通过volatile修饰的
transient volatile Node<K, V>[] table;
//扩容后的新table
private transient volatile Node<K, V>[] nextTable;
//桶中node的计数器
private transient volatile long baseCount;
//初始化或扩容的标识,用volatile修饰的
private transient volatile int sizeCtl;
//调整大小时要拆分的下一个表索引
private transient volatile int transferIndex;
//构造函数,初始化数组大小
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
this.sizeCtl = cap;
}
//构造函数,将一个map集合全部赋值
public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
this.sizeCtl = DEFAULT_CAPACITY;
putAll(m);
}
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
if (initialCapacity < concurrencyLevel) // Use at least as many bins
initialCapacity = concurrencyLevel; // as estimated threads
long size = (long) (1.0 + (long) initialCapacity / loadFactor);
int cap = (size >= (long) MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY : tableSizeFor((int) size);
this.sizeCtl = cap;
}
2.初始化
//初始化table数组
private final Node<K, V>[] initTable() {
Node<K, V>[] tab;
int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)
//sizeCtl值为-1时,表示正在初始化,因为sizeCtl是被volatile修饰的,所以一旦有一个线程在初始化,其他线程立马能感知到,那么就让出当前线程资源,没必要在这卡着,因为已经有线程在初始化了
Thread.yield(); // lost initialization race; just spin
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
//通过CAS将sizeCtl改为-1,并且已经获取到资源,能够进入if逻辑中
//给map的数组初始化
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K, V>[] nt = (Node<K, V>[]) new Node<?, ?>[n];
table = tab = nt;
sc = n - (n >>> 2);