ConcurrentHashMap实现原理
在JDK1.7中ConcurrentHashMap采用了【数组+Segment分段锁+单向链表】的方式实现。
1.Segment分段锁
ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表,同时又是一个ReentrantLock(Segment继承了ReentrantLock)。
2.内部结构
ConcurrentHashMap使用分段锁技术实现线程安全行,将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问。ConcurrentHashMap定位一个元素的过程需要进行两次Hash操作。第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部。
坏处:
这一种结构的带来的副作用是Hash的过程要比普通的HashMap要长
好处:
写操作的时候可以只对元素所在的Segment进行加锁即可,不会影响到其他的Segment,这样,在最理想的情况下,ConcurrentHashMap可以最高同时支持Segment数量大小的写操作(刚好这些写操作都非常平均地分布在所有的Segment上)。所以通过这种结构,ConcurrentHashMap的并发能力可以大大的提高。
JDK8中ConcurrentHashMap采用了【数组+链表+红黑树】的实现方式来设计,内部大量采用CAS操作。
CAS是compare and swap的缩写,即比较交换。cas是一种基于锁的操作,而且是乐观锁。
JDK8中彻底放弃了Segment转而采用的是Node,其设计思想也不再是JDK1.7中的分段锁思想。
Node:保存key,value及key的hash值的数据结构。其中value和next都用volatile修饰,保证并发的可见性。
在JDK8中ConcurrentHashMap的结构,由于引入了红黑树,使得ConcurrentHashMap的实现非常复杂,红黑树是一种性能非常好的二叉查找树,其查找性能为O(logN),但是其实现过程也非常复杂,而且可读性也非常差,早期完全采用链表结构时Map的查找时间复杂度为O(N),JDK8中ConcurrentHashMap在链表的长度大于某个阈值的时候会将链表转换成红黑树进一步提高其查找性能。
类定义
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
implements ConcurrentMap<K,V>, Serializable
并发接口ConcurrentMap定义
public interface ConcurrentMap<K,V> extends Map<K,V> {
按照key值获取对应的value值,如果不存在则返回defaultValue
default V getOrDefault(Object key, V defaultValue) {
V v;
return ((v = get(key)) != null) ? v : defaultValue;
}
迭代集合中的所有数据,参数为lambda表达式
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K,V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch (IllegalStateException ise) {
continue;
}
action.accept(k, v);
}
}
如果key值不存在则添加数据,否则不执行操作,并返回key对应的value值
V putIfAbsent(K key, V value);
按照key-value执行删除操作,如果key不存在或者key对应的value值不相等,则不执行操作
boolean remove(Object key, Object value);
按照key-oldValue执行修改操作,如果key不存在或者key对应的value不等于oldValue,则不执行操作
boolean replace(K key, V oldValue, V newValue);
按照key值修改value值
V replace(K key, V value);
按照lambda表达式的计算结果修改集合中的所有元素
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
forEach((k,v) -> {
while (!replace(k, v, function.apply(k, v))) {
if ( (v = get(k)) == null) {
break;
}
}
});
}
属性:
private static final int MAXIMUM_CAPACITY = 1 << 30; 最大容积值为2的30次方
private static final int DEFAULT_CAPACITY = 16;默认容积值
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;最大数组长度
private static final float LOAD_FACTOR = 0.75f; 默认负载因子
static final int TREEIFY_THRESHOLD = 8;树化阈值
static final int UNTREEIFY_THRESHOLD = 6;蜕化阈值
static final int MIN_TREEIFY_CAPACITY = 64;最小树化容积值
transient volatile Node<K,V>[] table;当前存储key-value的数组
存储数据的Node类型定义
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
构造器
public ConcurrentHashMap() {
}
public ConcurrentHashMap(int initialCapacity) {
this(initialCapacity, LOAD_FACTOR, 1);
}
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) // Use at least as many bins
initialCapacity = concurrencyLevel; // as estimated threads
long size = (long)(1.0 + (long)initialCapacity / loadFactor);
int cap = (size >= (long)MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY : tableSizeFor((int)size);
this.sizeCtl = cap;
}
put方法
public V put(K key, V value) {
return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
//不允许null键和null值,否则异常
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode()); //计算key对应的hash值
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh; K fk; V fv;
if (tab == null || (n = tab.length) == 0)
tab = initTable(); 由于采用的是延迟初始化数组的策略,所以执行数组的初始化操作
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else if (onlyIfAbsent // check first node without acquiring lock
&& fh == hash
&& ((fk = f.key) == key || (fk != null && key.equals(fk)))
&& (fv = f.val) != null)
return fv;
else {
V oldVal = null;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
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);
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;
}
}
else if (f instanceof ReservationNode)
throw new IllegalStateException("Recursive update");
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
本文详细介绍了JDK1.7和JDK8中ConcurrentHashMap的实现原理。1.7版采用数组+Segment分段锁+单向链表,通过分段锁提高并发性能;1.8版则采用数组+链表/红黑树,利用CAS操作和红黑树优化查找效率。在高并发场景下,ConcurrentHashMap提供了高效且线程安全的字典操作。

1万+

被折叠的 条评论
为什么被折叠?



