浅读java.util.Map及其实现类(三)
来源:互联网 发布:淘宝我的空间3d形象 编辑:程序博客网 时间:2024/06/01 20:26
ConcurrentHashMap
源码分析 JDK1.8
我们按照以下常用操作来分析其内部源码
public static void main(String[] args) { ConcurrentHashMap<String, Integer> ccMap = new ConcurrentHashMap<>(); ccMap.put("A", 12306); ccMap.remove("B"); ccMap.put("A", 456); System.out.printf("%s %d",ccMap.get("A"),ccMap.size()); ccMap.clear();}
new ConcurrentHashMap<K,V>
public ConcurrentHashMap() { //默认构造 } //MAXIMUM_CAPACITY = 1 << 30 最大容量 public ConcurrentHashMap(int initialCapacity) { //initialCapacity初始容量 if (initialCapacity < 0) throw new IllegalArgumentException(); int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1)); //tableSizeFor是通过一系列位运算得到的sizeCtl //关于运算可以参见 http://download.csdn.net/detail/crazyzxljing0621/9881657 中 3.2节 this.sizeCtl = cap; //sizeCtl代表一个标识 // 负数为初始化或正在调整大小 -1为正在初始化 反之则为 -N N = 1+正在参与调整大小的线程数 // table为NULL时则为初始表大小,初始化后记录下一次调整大小的值 } public ConcurrentHashMap(Map<? extends K, ? extends V> m) { this.sizeCtl = DEFAULT_CAPACITY; putAll(m); } public ConcurrentHashMap(int initialCapacity, float loadFactor) { this(initialCapacity, loadFactor, 1); } public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { //容量,加载因子,线程量(预计并发量) if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0) throw new IllegalArgumentException(); if (initialCapacity < concurrencyLevel) initialCapacity = concurrencyLevel; // 确保容量不能小于线程量 long size = (long)(1.0 + (long)initialCapacity / loadFactor); //通过容量和加载因子设定size,因子越大size越紧凑越小,可能的冲突就越大, //但是size过大也造成资源浪费 int cap = (size >= (long)MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : tableSizeFor((int)size); this.sizeCtl = cap; } //从构造函数发现并没有初始化容器的操作,都只是进行一些参数的计算和决策,真正的容器初始化在哪里呢?继续往下看ConcurrentHashMap.put(K)
public V put(K key, V value) { return putVal(key, value, false); } final V putVal(K key, V value, boolean onlyIfAbsent) { if (key == null || value == null) throw new NullPointerException(); //ConcurrentHashMap键值都不可为空 int hash = spread(key.hashCode());//分布哈希值降低冲突概率 int binCount = 0; for (Node<K,V>[] tab = table;;) {//首次new之后put进来table是有值的,值内为main在debug时由classLoad调用产生的指向的是堆栈K,V // Node<K,V> f; int n, i, fh; if (tab == null || (n = tab.length) == 0) tab = initTable(); else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { //根据tab和hash来计算目标位置是否有值,如果有则为Node<K,V> if (casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null))) //CAS直接放进去没有锁,直接放进去 break; //compare and swap,解决多线程并行情况下使用锁造成性能损耗的一种机制, //CAS操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配, //那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况, //它都会在CAS指令之前返回该位置的值。CAS有效地说明了“我认为位置V应该包含值A;如果包含该值,则将B放到这个位置; //否则,不要更改该位置,只告诉我这个位置现在的值即可。 --- from 百度CAS百科 } else if ((fh = f.hash) == MOVED)//当前正在进行调整状态 一起进行表扩容 tab = helpTransfer(tab, f); else { V oldVal = null; synchronized (f) { //锁住位置上的Node if (tabAt(tab, i) == f) { //核对这个Node if (fh >= 0) { //如果节点是链表存储 static final int MOVED = -1; // hash for forwarding nodes binCount = 1; for (Node<K,V> e = f;; ++binCount) { K ek; //比对hash是否一致 if (e.hash == hash &&((ek = e.key) == key || (ek != null && key.equals(ek)))) { //把旧值放起来 oldVal = e.val; if (!onlyIfAbsent) //把新值更新到原位置上的Node e.val = value; break; } Node<K,V> pred = e; if ((e = e.next) == null) { //如果一致无法匹配,最后新建一个Node放到当前位置Node的尾部,链表法解决hash冲突 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) { if (binCount >= TREEIFY_THRESHOLD) //当超过了TREEIFY_THRESHOLD=8这个阈值则把链表转为Tree treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } addCount(1L, binCount);//计数+1 return null; } public void putAll(Map<? extends K, ? extends V> m) { tryPresize(m.size());//创建一个合适大小的容器 遍历写入即可 for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) putVal(e.getKey(), e.getValue(), false); }//总的来说put使用了synchronized来确保插入新值,先确定当前hash位置下有没有值,如果没有直接插入,如果有则根据树或链表节点来插//当链表节点过长超过默认8的时则转为树节点//((fh = f.hash) == MOVED)当发现节点正在扩容时,则帮助一起扩容
ConcurrentHashMap.remove(K)
public boolean remove(Object key, Object value) { if (key == null) throw new NullPointerException(); return value != null && replaceNode(key, null, value) != null; } final V replaceNode(Object key, V value, Object cv) { int hash = spread(key.hashCode());//与put一样得到hash for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; if (tab == null || (n = tab.length) == 0 || (f = tabAt(tab, i = (n - 1) & hash)) == null) //表不存在或位置下本来就没有任何值 break; else if ((fh = f.hash) == MOVED) //同put,扩容状态,协助扩容 tab = helpTransfer(tab, f); else { V oldVal = null; boolean validated = false; synchronized (f) { //锁住要变化的位置值 if (tabAt(tab, i) == f) { if (fh >= 0) { //链表 validated = true; for (Node<K,V> e = f, pred = null;;) { K ek; if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { //param与位置上的值进行hash匹配 V ev = e.val; //得到旧值 if (cv == null || cv == ev || (ev != null && cv.equals(ev))) { //被替换的值与旧值hash匹配成功,当remove(K,V) replace(K,newV,oldV)使用equals // remove(K) 则传入的是 repalceNode(K,null,null) oldVal = ev; //得到旧值 if (value != null) //替换操作把新值覆盖旧值节点上的value e.val = value; else if (pred != null) // pred.next = e.next; else setTabAt(tab, i, e.next);//在tab的i位置下插入e.next其实就是把当前的抹了 } break; } pred = e; if ((e = e.next) == null) break; } } else if (f instanceof TreeBin) { //树 validated = true; TreeBin<K,V> t = (TreeBin<K,V>)f; TreeNode<K,V> r, p; if ((r = t.root) != null && (p = r.findTreeNode(hash, key, null)) != null) { V pv = p.val; if (cv == null || cv == pv || (pv != null && cv.equals(pv))) { oldVal = pv; if (value != null) p.val = value; else if (t.removeTreeNode(p)) //红黑树的方式删除tab上的节点 setTabAt(tab, i, untreeify(t.first)); } } } } } if (validated) { if (oldVal != null) { if (value == null) addCount(-1L, -1);//删除成功计数-1 return oldVal; } break; } } } return null; }
ConcurrentHashMap.clear()
public void clear() { long delta = 0L; // negative number of deletions int i = 0; Node<K,V>[] tab = table; while (tab != null && i < tab.length) { int fh; Node<K,V> f = tabAt(tab, i); //遍历并得到每个值的位置 if (f == null) ++i; else if ((fh = f.hash) == MOVED) { //正在扩容,协助扩容,i=0 从头循环 tab = helpTransfer(tab, f); i = 0; // restart } else { synchronized (f) {//锁住目标 if (tabAt(tab, i) == f) { Node<K,V> p = (fh >= 0 ? f :(f instanceof TreeBin) ?((TreeBin<K,V>)f).first : null); //根据链表与树得到值TreeBin是节点中存的值类型,TreeBin指向了TreeNode while (p != null) { --delta; p = p.next; //一个一个都删掉 } setTabAt(tab, i++, null); //tab每个位置都改成null } } } } if (delta != 0L) addCount(delta, -1); //计数修改 }
计数与扩容
private final void addCount(long x, int check) { CounterCell[] as; long b, s; //CounterCell一个计数单元容器由LongAdder与Striped64改编 if ((as = counterCells) != null || !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) { //compareAndSwapLong将 s=b+x更新到basecount这是一个安全的高并发的操作 CounterCell a; long v; int m; boolean uncontended = true; 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; } //上面就没看了反正就是一个jdk8的安全的高并发的计数增加 if (check <= 1) return; s = sumCount(); } if (check >= 0) { //check只有remove的时候传的是-1,其他时候都要检测是否要对容器进行扩容, 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); 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); //协助调整大小nt为需要扩到新表的下一个数据 } else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) transfer(tab, null); //自己开始扩容 s = sumCount();//看扩容后大小是否满足 } } }
总结
1. CAS 无锁算法2. synchronized代码块
3. 扩容标识
4. 协助扩容
5. TreeBin/TreeNode/Node
参考
http://www.cnblogs.com/Mainz/p/3546347.html
http://www.importnew.com/22007.html
http://blog.csdn.net/u010723709/article/details/48007881
http://blog.csdn.net/u011392897/article/details/60479937
http://wen866595.iteye.com/blog/2067912
阅读全文
0 0
- 浅读java.util.Map及其实现类(三)
- 浅读java.util.Map及其实现类(一)
- 浅读java.util.Map及其实现类(二)
- 浅读java.util.Map及其实现类(四)
- 浅读java.util.Map及其实现类(五)
- java Map及其实现类
- Java容器学习笔记(三) Map接口及其重要实现类学习总结
- Java学习笔记(三)Map接口及其重要实现类的用法
- java中的java.util.Map的实现类
- java之Map接口及其实现类
- java--17--Map及其实现类与子接口
- 【Java学习笔记】34:Map接口及其实现类
- java 双列集合Map及其子实现类
- Map集合及其实现类
- Map接口及其实现类
- java.util.Map翻译
- java.util 中 map
- java.util.Map接口
- 在Windows上配置MongoDB
- 【LeetCode】merge intervals
- bootstrap第一天
- 渗透利器
- 多屏适配解决方案
- 浅读java.util.Map及其实现类(三)
- 关于vs使用winddk开发包编译错误问题
- 游戏中能用的信号
- 原型尺寸规范总结
- 7大开源数据库利弊全对比,哪款才最适合你的?
- 为什么房子要坐南朝北,用地理的知识解释一下
- JavaScript学习总结——JavaScript函数(function)
- JS工具库封装:HTML5摄像头拍照组件的封装
- SDUT 1133 模拟计算器