一、概述
本章使用的是JDK8。
阅读本章请先了解HashMap的实现原理【Java】HashMap 的实现原理
1.1 ConcurrentHashMap跟HashMap,HashTable的对比
1. HashMap不是线程安全:
在并发环境下,可能会形成环状链表(扩容时可能造成,具体原因自行百度google或查看源码分析),导致get操作时,cpu空转,所以,在并发环境中使用HashMap是非常危险的,实现原理参考:【Java】HashMap 的实现原理
2. HashTable是线程安全的:
HashTable和HashMap的实现原理几乎一样,
差别:
- 1.HashTable不允许key和value为null;
- 2.HashTable是线程安全的。
HashTable线程安全的策略实现代价却比较大,get/put所有相关操作都是synchronized的,这相当于给整个哈希表加了一把大锁,多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞
3. ConcurrentHashMap是线程安全的:
直接采用transient volatile HashEntry<K,V>[] table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,并发控制使用Synchronized和CAS来操作,这样在多线程访问时不同段的数据时,就不会存在锁竞争了,这 样便可以有效地提高并发效率。这就是ConcurrentHashMap所采用的"分段锁"思想
二、结构图
由图可以看出,ConcurrentHashMap的结构是:数组 + 链表 + 红黑树
三、属性
1 // node数组最大容量:2^30=1073741824 2 private static final int MAXIMUM_CAPACITY = 1 << 30; 3 4 // 默认初始值,必须是2的幕数 5 private static final int DEFAULT_CAPACITY = 16; 6 7 //数组可能最大值,需要与toArray()相关方法关联 8 static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 9 10 //并发级别,遗留下来的,为兼容以前的版本 11 private static final int DEFAULT_CONCURRENCY_LEVEL = 16; 12 13 // 负载因子 14 private static final float LOAD_FACTOR = 0.75f; 15 16 // 链表转红黑树阀值,> 8 链表转换为红黑树 17 static final int TREEIFY_THRESHOLD = 8; 18 19 // 链表转红黑树时,最小容量阀值, >= 64 链表转换为红黑树 20 static final int MIN_TREEIFY_CAPACITY = 64; 21 22 // 树转链表阀值,小于等于6(tranfer时,lc、hc=0两个计数器分别++记录原bin、新binTreeNode数量,<=UNTREEIFY_THRESHOLD 则untreeify(lo)) 23 static final int UNTREEIFY_THRESHOLD = 6; 24 25 // 最小转移数组步长 26 private static final int MIN_TRANSFER_STRIDE = 16; 27 28 // 可以帮助调整大小的最大线程数,计算用到 29 private static int RESIZE_STAMP_BITS = 16; 30 31 // 2^15-1,help resize的最大线程数 32 private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1; 33 34 // 32-16=16,sizeCtl中记录size大小的偏移量 35 private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS; 36 37 // forwarding nodes的hash值 38 static final int MOVED = -1; 39 // 树根节点的hash值 40 static final int TREEBIN = -2; 41 // ReservationNode的hash值 42 static final int RESERVED = -3; 43 44 // 可用处理器数量 45 static final int NCPU = Runtime.getRuntime().availableProcessors(); 46 47 // 存放node的数组 48 transient volatile Node<K,V>[] table; 49 50 // 控制标识符,用来控制table的初始化和扩容的操作,不同的值有不同的含义 51 // 当为0时:代表当时的table还没有被初始化 52 // 当为负数时:-1代表正在初始化 53 // 小于-1时:实际值为resizeStamp(n) <<RESIZE_STAMP_SHIFT+2, 表明table正在扩容 54 // 当为正数时:未初始化,代表初始化大小,初始化完成后, 代表下一次扩容时的阀值, 默认为0.75*n 55 private transient volatile int sizeCtl; 56 57 // table容量从n扩到2n时, 是从索引n->1的元素开始迁移, transferIndex代表当前已经迁移的元素下标 58 private transient volatile int transferIndex; 59 60 // 基础计算器值 61 private transient volatile long baseCount; 62 63 // 计数器数组 64 private transient volatile CounterCell[] counterCells;
下面是 Node 类的定义,它是 HashMap 中的一个静态内部类,哈希表中的每一个 节点都是 Node 类型。我们可以看到,Node 类中有 4 个属性,其中除了 key 和 value 之外,还有 hash 和 next 两个属性。hash 是用来存储 key 的哈希值的,next 是在构建链表时用来指向后继节点的
1 static class Node<K,V> implements Map.Entry<K,V> { 2 final int hash; 3 final K key; 4 volatile V val; 5 volatile Node<K,V> next; 6 7 Node(int hash, K key, V val, Node<K,V> next) { 8 this.hash = hash; 9 this.key = key; 10 this.val = val; 11 this.next = next; 12 } 13 14 ...... 15 }
树节点TreeNode
1 static final class TreeNode<K,V> extends Node<K,V> { 2 TreeNode<K,V> parent; // red-black tree links 3 TreeNode<K,V> left; 4 TreeNode<K,V> right; 5 TreeNode<K,V> prev; // needed to unlink next upon deletion 6 boolean red; 7 8 9 ...... 10 }