1、根本变量
// Unsafe mechanics//Unsafe类是用来做cas操作的,都是native办法,代码由C++实现,上面的变量示意对应变量的偏移//例如:public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);// 示意对象o偏移地位offset的变量如果和期望值expected相等,把变量值设置为xprivate static final sun.misc.Unsafe U;private static final long SIZECTL;private static final long TRANSFERINDEX;private static final long BASECOUNT;private static final long CELLSBUSY;private static final long CELLVALUE;private static final long ABASE;private static final int ASHIFT;
1、table初始化
1、table会提早到第一次put时初始化,同过应用循环+CAS的套路,能够保障一次只有一个线程会初始化table。
2、在table为空的时候如果sizeCtl小于0,则阐明曾经有线程开始初始化了,其它线程通过Thread.yield()让出CPU工夫片,期待table非空即可。
3、否则应用CAS将sizeCtl的值换为-1,置换胜利则初始化table。
4、留神table的大小为sizeCtl,初始化后将sizeCtl的值设为n - (n >>> 2)即0.75n,这个值用来确定是否须要为table扩容。
//Initializes table, using the size recorded in sizeCtl.private final Node<K,V>[] initTable() { Node<K,V>[] tab; int sc; while ((tab = table) == null || tab.length == 0) { //判断是否曾经有线程在初始化,如果有则让出CPU,之后持续自旋判断 if ((sc = sizeCtl) < 0) Thread.yield(); // lost initialization race; just spin //cas操作设置sizeCtl的值 else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { try { //持续判断双重查看 if ((tab = table) == null || tab.length == 0) { int n = (sc > 0) ? sc : DEFAULT_CAPACITY; @SuppressWarnings("unchecked") //初始化table Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; table = tab = nt; sc = n - (n >>> 2);//设置sizeCtl=0.75n } } finally { sizeCtl = sc; } break; } } return tab;}
2、put操作(putVal办法)
1、首先计算key的hash值
2、判断table是否为空,如果是就初始化
3、依据hash值取余确定桶的地位,并判断桶是否为空,如果是空,通过cas操作设置进去
4、如果桶的第一个节点非空,并且hash=MOVED,阐明有线程正在进行扩容,调用helpTransfer帮忙扩容
5、对桶加锁并判断节点是链表还是树,依据不同状况插入节点
6、判断是否达到链表转树的阈值
7、统计节点数
final V putVal(K key, V value, boolean onlyIfAbsent) { if (key == null || value == null) throw new NullPointerException(); //1、首先计算key的hash值 int hash = spread(key.hashCode()); int binCount = 0; for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; //2、判断table是否为空,如果是就初始化 if (tab == null || (n = tab.length) == 0) tab = initTable(); //3、依据hash值取余确定桶的地位,并判断桶是否为空,如果是空 //通过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 } //4、如果桶的第一个节点非空,并且hash=MOVED,阐明有线程正在进行扩容, //调用helpTransfer帮忙扩容 else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); else { V oldVal = null; //5、对桶加锁并判断节点是链表还是树,依据不同状况插入节点 synchronized (f) { if (tabAt(tab, i) == f) {//保障多线程环境下的安全性,再一次查看 if (fh >= 0) {//hash值大于0,阐明是一个链表 binCount = 1;//记录链表的元素个数 for (Node<K,V> e = f;; ++binCount) { K ek; //判断key是否已在链表中,如果是间接替换value if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { oldVal = e.val; if (!onlyIfAbsent) e.val = value; break; } //否则阐明是一个新Key,插入表尾 Node<K,V> pred = e; if ((e = e.next) == null) { pred.next = new Node<K,V>(hash, key, value, null); break; } } } //不是链表节点就是树节点,调用树结点插入方法 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) { //6、判断是否达到链表转树的阈值 if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } //7、统计节点数 addCount(1L, binCount); return null;}
3、链表转树
1、判断链表节点是否小于64,如果是优先应用扩容
2、对桶加锁
3、顺次把链表节点汇合转成树结点汇合
4、把树结点汇合转成红黑树,这颗红黑树是链表和树结构的联合,它同样通过next援用串成了一个链表
private final void treeifyBin(Node<K,V>[] tab, int index) {//将tab[index]链表转树 Node<K,V> b; int n, sc; if (tab != null) { //1、判断链表节点是否小于64,如果是优先应用扩容 if ((n = tab.length) < MIN_TREEIFY_CAPACITY) tryPresize(n << 1); //为了多线程环境下的安全性,再次判断该桶是否为空,hash>0示意是链表 else if ((b = tabAt(tab, index)) != null && b.hash >= 0) { synchronized (b) {//2、对桶加锁 if (tabAt(tab, index) == b) {//加锁后再次判断 TreeNode<K,V> hd = null, tl = null; //3、顺次把链表节点汇合转成树结点 for (Node<K,V> e = b; e != null; e = e.next) { TreeNode<K,V> p = new TreeNode<K,V>(e.hash, e.key, e.val, null, null); if ((p.prev = tl) == null) hd = p; else tl.next = p; tl = p; } //4、把树结点汇合转成红黑树 setTabAt(tab, index, new TreeBin<K,V>(hd)); } } } }}
4、扩容
1、因为扩容容许并发操作,首先通过CPU数量,计算容许每个线程能解决的桶的数量stride,起码每个线程解决16个
2、申请nextTable数组
3、把nextTab设置为ForwardingNode,示意正在进行扩容操作
4、每个线程进来的线程去获取本人能解决区域,每个线程通过cas操作设置transferIndex变量,设置胜利则线程取得table中(transferIndex, transferIndex-strade)这一段的解决权限
相似下图这样的,反向调配的,先来的线程从前面获取桶段
5、循环顺次解决每一个桶:
5.1 如果以后桶首节点是链表,把链表节点重新分配到两个桶,一部分在原地位i,一分部在i+n地位
5.2 如果是红黑树,这里有点意思,同样是通过一个for像链表一样遍历,分成两局部,一个将放在i地位,一个将放在i+n地位。
那么问题来了,这里既然是树,为什么能够像链表一样操作呢?
这里是作者设计很奇妙的中央,拜服Doug lea大神,树的遍历十分不不便,而链表遍历很不便。TreeNode自身继承了Node,有一个next指针。当把链表节点数超过8,转成红黑树的时候,next指针不变,链表构造没有毁坏,所以这颗树自身是一个树和链表的结合体,既能够当链表用又能够当树用(这个设计有点相似LinkedHashMap,即时一个hashMap又是一个链表)。
相似下图这样的构造
这里被分成两个局部后,再依据这两个局部是否达到了树转列表的阈UNTREEIFY_THRESHOLD=6确定是转成链表还是组织成红黑树。private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) { int n = tab.length, stride; //取CPU的数量,确定每个线程迁徙的Node的数量,确保不会少于MIN_TRANSFER_STRIDE=16个 if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) stride = MIN_TRANSFER_STRIDE; // subdivide range //申请数组nextTable if (nextTab == null) { // initiating try { @SuppressWarnings("unchecked") Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; nextTab = nt; } catch (Throwable ex) { // try to cope with OOME sizeCtl = Integer.MAX_VALUE; return; } nextTable = nextTab; transferIndex = n; } int nextn = nextTab.length; //把nextTab设置为ForwardingNode,示意正在进行扩容操作 ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab); boolean advance = true; boolean finishing = false; // to ensure sweep before committing nextTab for (int i = 0, bound = 0;;) { Node<K,V> f; int fh; while (advance) { int nextIndex, nextBound; if (--i >= bound || finishing) advance = false; else if ((nextIndex = transferIndex) <= 0) { i = -1; advance = false; } /** nextIndex从n开始 这里每个线程进来的时候通过cas操作设置transferIndex变量, 如果胜利示意该线程解决nextIndex-stride这一段桶的迁徙 即 i=[nextIndex-1, nextIndex-stride] 这一段桶 */ //不相等,阐明不到最初一个线程,间接退出transfer办法 else if (U.compareAndSwapInt (this, TRANSFERINDEX, nextIndex, nextBound = (nextIndex > stride ? nextIndex - stride : 0))) { bound = nextBound; i = nextIndex - 1; advance = false; } } //判断是否解决实现所有的桶 if (i < 0 || i >= n || i + n >= nextn) { int sc; if (finishing) { nextTable = null; table = nextTab; sizeCtl = (n << 1) - (n >>> 1); return; } if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) { if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT) return; finishing = advance = true; i = n; // recheck before commit } } else if ((f = tabAt(tab, i)) == null) advance = casTabAt(tab, i, null, fwd); else if ((fh = f.hash) == MOVED) advance = true; // already processed else { synchronized (f) { if (tabAt(tab, i) == f) { Node<K,V> ln, hn; //hash>0示意链表,链表节点重新分配到两个桶,一部分在原地位i,一分部在i+n地位 if (fh >= 0) { int runBit = fh & n; Node<K,V> lastRun = f; //找到低位和高位链表的各自的尾节点,配合前面用头插法 for (Node<K,V> p = f.next; p != null; p = p.next) { int b = p.hash & n; if (b != runBit) { runBit = b; lastRun = p; } } if (runBit == 0) { ln = lastRun; hn = null; } else { hn = lastRun; ln = null; } //循环将链表拆分成两个链表,头插法 for (Node<K,V> p = f; p != lastRun; p = p.next) { int ph = p.hash; K pk = p.key; V pv = p.val; if ((ph & n) == 0) ln = new Node<K,V>(ph, pk, pv, ln); else hn = new Node<K,V>(ph, pk, pv, hn); } //将拆分后的列表通过cas操作设置为对应的桶中 setTabAt(nextTab, i, ln); setTabAt(nextTab, i + n, hn); setTabAt(tab, i, fwd); advance = true; } //该桶存储的是红黑树 else if (f instanceof TreeBin) { TreeBin<K,V> t = (TreeBin<K,V>)f; TreeNode<K,V> lo = null, loTail = null; TreeNode<K,V> hi = null, hiTail = null; int lc = 0, hc = 0; //这里的代码比拟有意思 //因为这里的红黑树是一个树和链表的结合体,TreeNode同时也是一个Node //所以这里能够像链表一样遍历,应用这种构造是为了不便遍历 //顺次取出树的节点,依据节点hash的后果分到位置和高位链表 //前面把链表转成树,同时会放弃链表的构造 for (Node<K,V> e = t.first; e != null; e = e.next) { int h = e.hash; TreeNode<K,V> p = new TreeNode<K,V> (h, e.key, e.val, null, null); if ((h & n) == 0) { if ((p.prev = loTail) == null) lo = p; else loTail.next = p; loTail = p; ++lc; } else { if ((p.prev = hiTail) == null) hi = p; else hiTail.next = p; hiTail = p; ++hc; } } //把树拆分成了低位(放在地位i)和高位(地位i+n)链表,这里判断两个链表长度是否达到转成树的阈值 ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) : (hc != 0) ? new TreeBin<K,V>(lo) : t; hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) : (lc != 0) ? new TreeBin<K,V>(hi) : t; //拆分后的构造设置到相应的桶中 setTabAt(nextTab, i, ln); setTabAt(nextTab, i + n, hn); setTabAt(tab, i, fwd); advance = true; } } } } }}
5、统计元素数量
1、简略通过CouterCell数组的和,因为可能有其余线程在进行增删操作,这个数据并不是准确的。
final long sumCount() { CounterCell[] as = counterCells; CounterCell a; long sum = baseCount; if (as != null) { for (int i = 0; i < as.length; ++i) { if ((a = as[i]) != null) sum += a.value; } } return sum;}