## ConcurrentHashMap1.8与1.7的区别 1. 去除segment分段锁 2. synchronized+cas 保证node节点线程安全问题 hashmap1.8与ConcurrentHashMap1.8基础数据结构相同 数组+链表+红黑树 区别: hashtable:对我们整个table数组上锁 ConcurrentHashMap:对node节点上锁 多个线程同时put key的时候,如果多个key都落入到同一个index node结点的时候,会导致锁的竞争,反之,则不会。 注意: 计算index只需要一次 ## put方法详解 ```java final V putVal(K key, V value, boolean onlyIfAbsent) { // 不支持key为空 if (key == null || value == null) throw new NullPointerException(); int hash = spread(key.hashCode()); // 链表转换为红黑树的阈值 int binCount = 0; // 死循环,自旋。table本身带有volatile关键字,即使读取最新主内存数据,保证线程可见性 for (ConcurrentHashMap.Node[] tab = table;;) { ConcurrentHashMap.Node f; int n, i, fh; if (tab == null || (n = tab.length) == 0) // 初始化table tab = initTable(); // 第二次循环,开始分治 // 当前链表是否为null,没有发生index冲突 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { // 这里会使用cas锁 if (casTabAt(tab, i, null, new ConcurrentHashMap.Node(hash, key, value, null))) break; // no lock when adding to empty bin } // 并发扩容会辅助扩容 else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); else { V oldVal = null; // 发生冲突时,会使用synchronized锁 synchronized (f) { // 再次查询一次,如果该节点被删除就会导致不同步 if (tabAt(tab, i) == f) { if (fh >= 0) { binCount = 1; for (ConcurrentHashMap.Node e = f;; ++binCount) { K ek; // 如果key值相同,直接修改 if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { oldVal = e.val; if (!onlyIfAbsent) e.val = value; break; } ConcurrentHashMap.Node pred = e; if ((e = e.next) == null) { pred.next = new ConcurrentHashMap.Node(hash, key, value, null); break; } } } else if (f instanceof ConcurrentHashMap.TreeBin) { ConcurrentHashMap.Node p; binCount = 2; if ((p = ((ConcurrentHashMap.TreeBin)f).putTreeVal(hash, key, value)) != null) { oldVal = p.val; if (!onlyIfAbsent) p.val = value; } } } } if (binCount != 0) { if (binCount >= TREEIFY_THRESHOLD) // 转换为红黑树 treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } addCount(1L, binCount); return null; } ``` cas使用时候:没有发生冲突的时候 synchronized使用:index发生冲突的时候 ## 初始化table原理 ```java private final Node[] initTable() { Node[] tab; int sc; // 自旋 while ((tab = table) == null || tab.length == 0) { // 如果发现其他线程正在扩容,当前线程释放CPU执行权 if ((sc = sizeCtl) < 0) Thread.yield(); // lost initialization race; just spin // cas操作,修改当前的sizeCtl为-1 else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { try { if ((tab = table) == null || tab.length == 0) { // 默认大小为16 int n = (sc > 0) ? sc : DEFAULT_CAPACITY; @SuppressWarnings("unchecked") // 对table长度做默认初始化 Node[] nt = (Node[])new Node[n]; table = tab = nt; // 16-4=12 sc = n - (n >>> 2); } } finally { sizeCtl = sc; } break; } } return tab; } ``` SIZECTL:默认值为0,用来控制table的初始化和扩容操作,具体应用在后续会体现出来 -1:代表table正在初始化 N:表示有N-1个线程正在进行扩容操作 其余的情况: 1. 如果table未初始化,表示table需要初始化的大小。0 2. 如果table初始化完成。表示table的容量,默认是table大小的0.75倍 ## addCount分析 ```java private final void addCount(long x, int check) { CounterCell[] as; long b, s; // basecount就是size的大小 if ((as = counterCells) != null || !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) { CounterCell a; long v; int m; boolean uncontended = true; // 修改线程自己的value // 线程的随机数&m 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; s = sumCount(); } // 这行支持并发扩容 if (check >= 0) { Node[] tab, nt; int n, sc; while (s >= (long)(sc = sizeCtl) && (tab = table) != null && (n = tab.length) < MAXIMUM_CAPACITY) { int rs = resizeStamp(n); 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); } else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) transfer(tab, null); s = sumCount(); } } } ``` 如果多个线程对size cas ++的情况下,会导致CPU飙升 ## ConcurrentHashMap为什么去除segment锁? 因为占用太多内存,而且效率较低。 ConcurrentHashMap1.7需要计算两次index,效率低 ## ConcurrentHashMap1.8为什么使用synchronized锁? synchronized在jdk1.6之后会有锁的升级,lock不自带自旋。 不需要自己写!