并发编程19
HashMap
- 掌握map首先要掌握list的原理
ArrayList源码
- add方法,首先判断是否装满,装满了,则扩容,没装满则直接添加,实际就是操作数组
LinkedList源码
-
比ArrayList要复杂,是以链表实现的,每一个节点都是一个Node类,是双向链表
-
class Node<T>{ Node pre; T t; Node next; }
-
add方法
- 首先创建Node对象,如果头节点是空的,则设置头节点,同时尾节点也是添加的第一个节点
- 创建第二个节点时,首先把头节点设置成这个新节点的前一个节点,同时对这个新节点赋值,然后再把第一个节点的下一个节点设置为这个新节点,同时令尾节点等于新节点
HashMap源码
- 是ArrayList和LinkedList的组合
- 像ArrayList那样,但是每一个数组元素都是一个Entry,而entry是继承了键值对属性的Node,而Node可以看做LinkedList,键值对中的key,存放的是hash值。
ConcurrentHashMap源码
一个坑
-
new时传入容量大小是32,但是真正的初始容量是64?
-
jdk1.7中32就是32,jdk1.8中32就是64
-
hashMap你指定容量大小是多少就是多少,不管jdk版本
-
public ConcurrentHashMap(int initialCapacity) { if (initialCapacity < 0) throw new IllegalArgumentException(); // jdk1.8初始化大小是(执定值+ 执定值/2 +1) 的最大最近的2的幂次方 tableSizeFor:输入低于最大容量的数c,返回大于等于且最接近c的2的幂次数。 int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1)); this.sizeCtl = cap; }
sizeCtl
-
// sizeCtl这个参数很重要 // sizeCtl为0 代表数组没有初始化 // sizeCtl为正数 数组已经初始化,记录的是数组的扩容阈值,如果没有初始化,记录的是数组初始容量 // sizeCtl 为-1 代表数组正在初始化 // sizeCtl 为负数,并且不是-1,表示数组正在扩容
putVal
-
final V putVal(K key, V value, boolean onlyIfAbsent) { if (key == null || value == null) throw new NullPointerException(); // 算出hash值 这个值一定是个正数 0x7fffffff; // f的二进制为:1111,7的二进制位0111 换算完之后最高位为0,所以最终值肯定为正数,最高位为0,无论怎么与,算出都是0,所以肯定是正数 int hash = spread(key.hashCode()); int binCount = 0; // 是一个死循环 // table是node<k,v>数组 // 由于里面都是else if,第一个线程创建完数组后,又会再次进入for循环 for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; // 第一个线程进来的第一次添加必然为true if (tab == null || (n = tab.length) == 0) // 创建数组 // 添加的时候才会去初始化数组 tab = initTable(); // 拿出次key hash位置的value,如果为空则去添加 // tabAt方法---可以认为是随机取出这个数组里面的一个值(通过哈希算法) // 此时刚刚创建也是null的 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { // 判断这个hash位置的内存中的值是否为null,如果真为null,则把新建的Node赋值给它 // 如果此时有别的线程也hash出来是在相同的数组位置,此时cas不成功 if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break; // no lock when adding to empty bin } // 如果此判断成立 说明此数组正在扩容,则去协助扩容 // 扩容时,迁移完成的数组某个位置会加个MOVED(-1) else if ((fh = f.hash) == MOVED) // 多线程扩容 tab = helpTransfer(tab, f); else { // 如果hash算出来的数组位置相同,并且有值,就会进入这里 V oldVal = null; // 锁住此位置 // 只锁住加入的数组中的某个位置 // 而hashTable是锁住整个数组 synchronized (f) { // 双重检验,如果红黑树改变掉了,锁就无效了 // 再次判断是因为 此位置的value有可能变为红黑树,锁就没用了 if (tabAt(tab, i) == f) { // 如果成立说明是链表 // 数组中的元素除了是链表,也有可能是红黑树 if (fh >= 0) { binCount = 1; // f是当前添加的数组中的对象 for (Node<K,V> e = f;; ++binCount) { K ek; // 如果hash和key都一样,并且非空 if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { oldVal = e.val; // onlyIfAbsent // 如果key相同是否覆盖旧值 // 默认是覆盖 if (!onlyIfAbsent) e.val = value; break; } 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) { //如果链表的长度>=8 则变为黑红树 if (binCount >= TREEIFY_THRESHOLD) //但是如果数组的长度小于64,那么则不变为红黑树,则扩容 treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } // 维护集合长度,判断是否达到扩容阈值,如果是,则扩容 // binCount表示的是当前加入数组所在位置的链表长度 addCount(1L, binCount); return null; }
initTable
-
// 可以实现无锁的并发 private final Node<K,V>[] initTable() { Node<K,V>[] tab; int sc; while ((tab = table) == null || tab.length == 0) { // sizeCtl小于0,代表 数组正在初始化或者正在扩容 // 如果传了是最接近的2的n次幂,不传是0,所以第一次进来不成立 if ((sc = sizeCtl) < 0) // 会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行) // 即使抢到了,也没有执行创建数组的操作 Thread.yield(); // compareAndSwapInt(Object var1, long var2, int var4, int var5); // o:目标Java变量引用。offset:目标Java变量中的目标属性的偏移地址。expected:目标Java变量中的目标属性的期望的当前值。x:目标Java变量中的目标属性的目标更新值。 // SIZECTL就是sizeCtl的内存地址,绕过jvm直接从内存拿值 else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { try { // 双重判断是为了不重复创建 因为一个线程创建完后,会更改sizeCtl值 // cas改为-1时,其余的线程都会在上面阻塞,创建为数组后,此时已经变成大于0的数,又会再次进入else if,防止重复创建 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; // 可以换算为 n*0.75 n >>> 2= 四分之一 ,此时sizeCtl=扩容阈值 // sc = n-0.25n = 0.75*n // 为什么是0.75作为扩容阈值? sc = n - (n >>> 2); } } finally { sizeCtl = sc; } break; } } return tab; }
addCount
-
// 计数的方式有两种 // 一是baseCount // 二是一个数组,多线程加结果可能不对 private final void addCount(long x, int check) { CounterCell[] as; long b, s; // 首先加baseCount // 加baseCount成功不会进入 加baseCount失败则进入 // counterCells就是计数的数组 if ((as = counterCells) != null || !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) // 如果多线程cas更新失败则进入if { CounterCell a; long v; int m; boolean uncontended = true; // 并发时更新失败进来的第一个线程,as肯定是空的,如果进入if if (as == null || (m = as.length - 1) < 0 || (a = as[ThreadLocalRandom.getProbe() & m]) == null || !(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) { fullAddCount(x, uncontended); return; } if (check <= 1) return; // 算出当前map中有多少元素,判断是否达到扩容阈值,如果是,则扩容 // 把baseCount和计数数组里面的值全部累加起来 s = sumCount(); } // 判断要不要扩容 // 扩容是最难的 if (check >= 0) { Node<K,V>[] tab, nt; int n, sc; while (s >= (long)(sc = sizeCtl) && (tab = table) != null && (n = tab.length) < MAXIMUM_CAPACITY) { //创建一个标识值 int rs = resizeStamp(n); //第一个线程进来不可能小于0 if (sc < 0) { if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || (nt = nextTable) == null || transferIndex <= 0) break; if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) //协助扩容 transfer(tab, nt); } //将SIZECTL 改为一个负数 else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) //发起扩容(只有可能是第一个线程执行) transfer(tab, null); s = sumCount(); } } }
fullAddCount
-
private final void fullAddCount(long x, boolean wasUncontended) { int h; //以此线程生成一个随机数,作为数组下标 if ((h = ThreadLocalRandom.getProbe()) == 0) { ThreadLocalRandom.localInit(); // force initialization h = ThreadLocalRandom.getProbe(); wasUncontended = true; } boolean collide = false; // True if last slot nonempty for (;;) { CounterCell[] as; CounterCell a; int n; long v; // 第一次进来肯定为false(一旦有线程进入,则为true,那么数组已经创建),执行下面的else if // 第二次进来,不为null if ((as = counterCells) != null && (n = as.length) > 0) { // 随机取值,如果为空,则赋值 if ((a = as[(n - 1) & h]) == null) { if (cellsBusy == 0) { // Try to attach new Cell CounterCell r = new CounterCell(x); // Optimistic create // 此处的U.compareAndSwapInt(this, CELLSBUSY, 0, 1)可以认为是一个锁 if (cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { boolean created = false; try { // Recheck under lock CounterCell[] rs; int m, j; if ((rs = counterCells) != null && (m = rs.length) > 0 && rs[j = (m - 1) & h] == null) { // 赋值给计数数组,直接放个1 rs[j] = r; created = true; } } finally { cellsBusy = 0; } if (created) break; continue; // Slot is now non-empty } } collide = false; } else if (!wasUncontended) // CAS already known to fail wasUncontended = true; // Continue after rehash // 此数组已经有值,那么直接累加---累加操作 else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x)) break; // 避免数组扩容,当目前的数组长度>=CPU核数时 // 此处的扩容是指对计数数组进行扩容 // 如果n>=NCPU,只加一次自旋次数? // 如果数组数已经大于核心数(并行数),就没有必要对计数数组进行扩容了 else if (counterCells != as || n >= NCPU) collide = false; // At max size or stale else if (!collide) collide = true; // 数组扩容 else if (cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { try { if (counterCells == as) {// Expand table unless stale CounterCell[] rs = new CounterCell[n << 1]; for (int i = 0; i < n; ++i) rs[i] = as[i]; counterCells = rs; } } finally { cellsBusy = 0; } collide = false; continue; // Retry with expanded table } h = ThreadLocalRandom.advanceProbe(h); } // 创建计数数组,并且赋值(并发cas更新失败进来的第一个线程) else if (cellsBusy == 0 && counterCells == as && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { boolean init = false; try { // Initialize table // 双重校验,不重复创建 if (counterCells == as) { // 创建了一个数组大小为2的数组---counterCells // 其中一个是1,一个是空 CounterCell[] rs = new CounterCell[2]; rs[h & 1] = new CounterCell(x); counterCells = rs; init = true; } } finally { cellsBusy = 0; } if (init) break; } //数组正在创建 对baseCount++ else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x)) break; // Fall back on using base } }
transfer
-
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) { int n = tab.length, stride; // 因为是多线程扩容 // 判断每一个线程要领多少个任务 // stride是每一个线程要迁移多少个值 // 单线程也是分段,一段一段来迁移的 // 多线程扩容,每个线程最少领16个格子 if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) // stride肯定大于16 stride = MIN_TRANSFER_STRIDE; // initiating if (nextTab == null) { 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; ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab); boolean advance = true; // 所有线程是否完成扩容 boolean finishing = false; 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; } //将transferIndex设置为 老数组的长度-16 或者 0 else if (U.compareAndSwapInt (this, TRANSFERINDEX, nextIndex, nextBound = (nextIndex > stride ? nextIndex - stride : 0))) { //老数组的长度-16 或者 0 bound = nextBound; //老数组的长度-1 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 } } //如果当前位置为空 那么赋值fwd 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; if (fh >= 0) { int runBit = fh & n; // 重点 // 区别数据 假设现在迁移的位置 是个链表 size是6 那么可能还是3个保留在原来的位置 还有3个放置到原来的位置+老数组的长度位置上 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); } 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; 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; } } 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; } } } } } }
-
// 重点
-
// 区别数据 假设现在迁移的位置 是个链表 size是6 那么可能还是3个保留在原来的位置 还有3个放置到原来的位置+老数组的长度位置上
-
// 把链表长度变短
-
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); } setTabAt(nextTab, i, ln); setTabAt(nextTab, i + n, hn); setTabAt(tab, i, fwd); advance = true;
-
// (resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2) // (sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT