ConcurrentHashMap(JDK1.8)
- 流程图:
- ConcurrentHashMap属性列表
sizeCtl:表初始化和调整大小控制字段.如果为负数,则表正在初始化或调整大小,此时写操作将自旋
Node的Hash节点数据节点类型:MOVED=-1,列表的格式正在转换;TREEBIN=-2,树节点的Hash;RESERVED=-3,临时Hash;HASH_BITS=0x7fffffff,正常节点Hash
- ConcurrentHashMap的putVal源码
final V putVal(K key, V value, boolean onlyIfAbsent) { if (key == null || value == null) throw new NullPointerException(); //将key转换为hash码,尽可能的保证hash的均匀分布,且hash值必须大于0,因为小于0的是特殊hash int hash = spread(key.hashCode()); int binCount = 0; for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; //如果tab为空 if (tab == null || (n = tab.length) == 0) //则创建table tab = initTable(); //查询tab中第1个元素,如果为空,则将第一个元素直接插入 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 } //hash获取,在JDK1.8中,ConcurrentHashMap中所有列表的第一个node作为锁 //内置了不同的hash码,来标记不同的状态, //将节点的元素数据移植到新表上面 else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); else { V oldVal = null; //对节点f加锁 synchronized (f) { //二次验证节点f if (tabAt(tab, i) == f) { //如果f节点的Hash值正常,也就是大于0 if (fh >= 0) { 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) 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; } } } //如果节点f为树类型节点 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; } } } } //如果链表节点个数大于8且table数据个数大于默认上限64,则转化为红黑树 //如果小于64,则只是尝试扩容 if (binCount != 0) { if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } addCount(1L, binCount); return null; } • ConcurrentHashMap#putVal • initTable()源码 private final Node<K,V>[] initTable() { Node<K,V>[] tab; int sc; while ((tab = table) == null || tab.length == 0) { //获取系统的sizeCtl赋值给sc,如果sizeCtl小于0,则认为此时的数据已经被其余线程修改,自身进入等待...... if ((sc = sizeCtl) < 0) Thread.yield(); // 当前线程自旋 //CAS操作 //获取当前对象的SIZECTL与sc比较,如果系统的SIZECTL等于sc,则将SIZECTL修改为-1 else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { try { if ((tab = table) == null || tab.length == 0) { //为Table设置元素存取上限 int n = (sc > 0) ? sc : DEFAULT_CAPACITY; @SuppressWarnings("unchecked") Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; table = tab = nt; sc = n - (n >>> 2); } } finally { sizeCtl = sc; } break; } } return tab; }
- Unsafe.compareAndSwapInt(Object var1, long var2, int var4, int var5);
- 乐观锁CAS(比较交换):如果对象var1中的属性var2的值与期望值var4一致,则认为数据在内存没有被修改,此时把var5赋值给var2
- CAS流程图:
- 无锁CAS与锁的区别
- 在性能方面:无锁CAS不需要线程之间切换的开销;但是无锁CAS可能会导致问题一致处于无解的状态,进而一直自旋,导致CPU资源一直占用
- ConcurrentHashMap、 HashMap、Hashtable对比
ConcurrentHashMap | HashMap | Hashtable | |
数据安全? | 安全 | 不安全 | 安全 |
性能? | 快 | 快 | 慢 |
- HashTable通过synchronized关键字实现了线程安全,但是HashTable在方法级别上使用了synchronized,会使线程独占一张HashTable,性能较低
- ConcurrentHashMap通过synchronized以及CAS来实现线程安全,但是ConcurrentHashMap是通过对数据节点使用了synchronized,其相对于HashTable来讲,封锁的粒度更低,可以保证线程不是独占HashTable,而是独占了某一部分数据,对于其余的数据依然可以访问,因此其性能相较于HashTable来说,性能更好