ConcurrentHashMap
1.实现原理
2.数据结构
3.增删改查
一.实现原理
锁分离
ConcurrentHashMap使用分段锁技术,将数据分段存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,
其他段的数据也能被其他线程访问,能够实现真正的并发访问。
对于一个key,需要经过三次(为什么要hash三次下文会详细讲解)hash操作,才能最终定位这个元素的位置,这三次hash分别为:
对于一个key,先进行一次hash操作,得到hash值h1,也即h1 = hash1(key);
将得到的h1的高几位进行第二次hash,得到hash值h2,也即h2 = hash2(h1高几位),通过h2能够确定该元素的放在哪个Segment;
将得到的h1进行第三次hash,得到hash值h3,也即h3 = hash3(h1),通过h3能够确定该元素放置在哪个HashEntry。
除了value不是final的,其它值都是final的,因此所有的节点的修改只能从头部开始
三.增删改查
remove
1.定位到segment
2.加锁
3.定位到要删除的数据
put
1.通过hash算法得到对应的分段
2.向分段中push值
3.判断是否需要rehash
4.修改modcount
5.插入值,一律添加到Hash链的头部。
6.修改count
get
1.定位到segment
2.阅读count,保证happens_before(不是绝对安全)
3.获得值
4.如果为空则加锁重新读
size
1.先给3次机会,不lock所有的Segment,遍历所有Segment,累加各个Segment的大小得到整个Map的大小,
如果某相邻的两次计算获取的所有Segment的更新的次数(每个Segment都有一个modCount变量,
这个变量在Segment中的Entry被修改时会加一,通过这个值可以得到每个Segment的更新操作的次数)是一样的,
说明计算过程中没有更新操作,则直接返回这个值。
2.如果这三次不加锁的计算过程中Map的更新次数有变化,则之后的计算先对所有的Segment加锁,再遍历所有Segment计算Map大小,最后再解锁所有Segment
1.实现原理
2.数据结构
3.增删改查
一.实现原理
锁分离
ConcurrentHashMap使用分段锁技术,将数据分段存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,
其他段的数据也能被其他线程访问,能够实现真正的并发访问。
对于一个key,需要经过三次(为什么要hash三次下文会详细讲解)hash操作,才能最终定位这个元素的位置,这三次hash分别为:
对于一个key,先进行一次hash操作,得到hash值h1,也即h1 = hash1(key);
将得到的h1的高几位进行第二次hash,得到hash值h2,也即h2 = hash2(h1高几位),通过h2能够确定该元素的放在哪个Segment;
将得到的h1进行第三次hash,得到hash值h3,也即h3 = hash3(h1),通过h3能够确定该元素放置在哪个HashEntry。
二.数据结构
public class ConcurrentHashMap<K, V> {
final int segmentMask; // 段掩码 等于数组长度-1 默认为15
final int segmentShift; // 段偏移量 等于 32 - 数组长度从1向左移位的次数, 长度默认为16所以默认为32-4=28
final Segment<K,V>[] segments;
}
static final class Segment<K,V> extends ReentrantLock{
transient volatile int count; // 统计该段数据的个数
transient int modCount; // 统计段结构改变的次数
transient int threshold; // 表示需要进行rehash的界限值
transient volatile HashEntry<K,V>[] table;
final float loadFactor; // 表示负载因子
}
static final class HashEntry<K,V> {
final K key;
final int hash;
volatile V value;
final HashEntry<K,V> next;
}
除了value不是final的,其它值都是final的,因此所有的节点的修改只能从头部开始
三.增删改查
remove
1.定位到segment
2.加锁
3.定位到要删除的数据
4.将要删除节点的前面所有节点整个复制一遍,最后一个节点指向要删除结点的下一个结点。
public V remove(Object key) {
<span style="white-space:pre"> </span>int hash = hash(key.hashCode());
<span style="white-space:pre"> </span>return segmentFor(hash).remove(key, hash, null);
}
V remove(Object key, int hash, Object value) {
lock();
try {
int c = count - 1;
HashEntry<K,V>[] tab = table;
int index = hash & (tab.length - 1);
HashEntry<K,V> first = tab[index];
HashEntry<K,V> e = first;
while (e != null && (e.hash != hash || !key.equals(e.key)))
e = e.next;
V oldValue = null;
if (e != null) {
V v = e.value;
if (value == null || value.equals(v)) {
oldValue = v;
// All entries following removed node can stay
// in list, but all preceding ones need to be
// cloned.
++modCount;
HashEntry<K,V> newFirst = e.next;
for (HashEntry<K,V> p = first; p != e; p = p.next)
newFirst = new HashEntry<K,V>(p.key, p.hash,
newFirst, p.value);
tab[index] = newFirst;
count = c; // write-volatile
}
}
return oldValue;
} finally {
unlock();
}
}
put
1.通过hash算法得到对应的分段
2.向分段中push值
3.判断是否需要rehash
4.修改modcount
5.插入值,一律添加到Hash链的头部。
6.修改count
get
1.定位到segment
2.阅读count,保证happens_before(不是绝对安全)
3.获得值
4.如果为空则加锁重新读
size
1.先给3次机会,不lock所有的Segment,遍历所有Segment,累加各个Segment的大小得到整个Map的大小,
如果某相邻的两次计算获取的所有Segment的更新的次数(每个Segment都有一个modCount变量,
这个变量在Segment中的Entry被修改时会加一,通过这个值可以得到每个Segment的更新操作的次数)是一样的,
说明计算过程中没有更新操作,则直接返回这个值。
2.如果这三次不加锁的计算过程中Map的更新次数有变化,则之后的计算先对所有的Segment加锁,再遍历所有Segment计算Map大小,最后再解锁所有Segment