以下ConcurrentHashMap源码的解析都是基于java8来讲解的。
ConcurrentHashMap的线程安全性是通过synchronized+CAS实现的。ConcurrentHashMap的数据结构和HashMaori大致相同,下面主要讲一下不同的地方
常量
// 这个属性用于控制table的初始化和扩容
// sizeCtl<0表示table正在初始化或者扩容
// sizeCtl=-1 表示table正在初始化
// sizeCtl<-1 表示table正在扩容
// sizeCtl默认为0
private transient volatile int sizeCtl;
// 默认并发度,同时允许多少个线程访问
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
// 表示node正在被移动
static final int MOVED = -1;
// 表示node正在进行树化
static final int TREEBIN = -2;
// 表示node运行computeIfAbsent等方法
static final int RESERVED = -3;
方法
-
initTable方法
table的初始化是懒惰的,是在put操作的时候进行的,如果put时发现table没有初始化,才进行table的初始化操作。
private final Node<K,V>[] initTable() { Node<K,V>[] tab; int sc; while ((tab = table) == null || tab.length == 0) { // sizeCtl<0说明有线程正在初始化table,所以当前线程让出CPU if ((sc = sizeCtl) < 0) Thread.yield(); // lost initialization race; just spin //sizeCtl不小于0的话,通过cas把sizeCtl改为-1 else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { try { //这里进行table的二次校验,是因为多线程下,有可能两个线程同时都判断table为null, //一个线程cas设置sizeCtl=-1成功进行初始化,另一个竞争sizeCtl, //等第一个线程初始化完成后,第二个又进行了初始化 if ((tab = table) == null || tab.length == 0) { //初始化时,sc=-1,所以n=DEFAULT_CAPACITY(16) int n = (sc > 0) ? sc : DEFAULT_CAPACITY; @SuppressWarnings("unchecked") Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; table = tab = nt; //因为sizeCtl还用于扩容,所以这里设置sc的值为阈值 //因为负载因子是0.75,也就是1-0.25,所以用n>>>2表示0.25这部分 sc = n - (n >>> 2); } } finally { sizeCtl = sc; } break; } } return tab; } -
get方法
首先通过spread方法计算出hash值,然后计算出所在table的index值
public V get(Object key) { Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek; int h = spread(key.hashCode()); //判断所属index的值不为空 if ((tab = table) != null && (n = tab.length) > 0 && (e = tabAt(tab, (n - 1) & h)) != null) { //首先判断node的头是不是要找的值 if ((eh = e.hash) == h) { if ((ek = e.key) == key || (ek != null && key.equals(ek))) return e.val; } //是红黑树的话,采用红黑树的方式查找 else if (eh < 0) return (p = e.find(h, key)) != null ? p.val : null; //while循环node链,找到hash相同且key equal的值 while ((e = e.next) != null) { if (e.hash == h && ((ek = e.key) == key || (ek != null && key.equals(ek)))) return e.val; } } return null; } -
put方法
put方法通过synchronized+CAS的方式实现线程安全,它锁住的是table的某个index下的值,所以不会影响到其他的值,所以锁的粒度很小
final V putVal(K key, V value, boolean onlyIfAbsent) { if (key == null || value == null) throw new NullPointerException(); int hash = spread(key.hashCode()); int binCount = 0; for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; //table为空的话,初始化table if (tab == null || (n = tab.length) == 0) tab = initTable(); //如果没有hash冲突的话,直接通过cas设置值 //如果cas失败,则可能是其他线程在插入,则继续下一步 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break; // no lock when adding to empty bin } //如果table[i]的hash值为-1,则表示有其他线程在扩容,则帮助其扩容 else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); else { V oldVal = null; //hash冲突之后,使用synchronized锁住所在index的node synchronized (f) { if (tabAt(tab, i) == f) { //fh>=0说明是链表结构 if (fh >= 0) { binCount = 1; //循环链表,put值 for (Node<K,V> e = f;; ++binCount) { K ek; //冲突的话,则覆盖旧值 if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { oldVal = e.val; if (!onlyIfAbsent) e.val = value; break; } Node<K,V> pred = e; //node链走到结尾也没有冲突的话,则构造新node,加到尾部 if ((e = e.next) == null) { pred.next = new Node<K,V>(hash, key, value, null); break; } } } //fh<0说明是红黑树结构,采用红黑树的方式put else if (f instanceof TreeBin) { Node<K,V> p; binCount = 2; if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) { oldVal = p.val; if (!onlyIfAbsent) p.val = value; } } } } if (binCount != 0) { //检查node链的长度,超过8则进行树化 if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } addCount(1L, binCount); return null; }