java.util.concurrent.ConcurrentHashMap

来源:互联网 发布:js表单正则表达式 编辑:程序博客网 时间:2024/05/10 18:18

 

一个一个类的学习,就不信整不明白你了。。。

ConcurrentHashMap

ConcurrentHashMap是线程安全的HashMap的实现。

1)添加

put(Object key , Object value)

ConcurrentHashMap并没有采用synchronized进行控制,而是使用了ReentrantLock。

public V put(K key, V value) {if (value == null)throw new NullPointerException();int hash = hash(key.hashCode());return segmentFor(hash).put(key, hash, value, false);}


 

这里计算出key的hash值,根据hash值获取对应的数组中的segment对象。接下来的工作都交由segment完成。

segment可以看成是HashMap的一个部分,(ConcurrentHashMap基于concurrencyLevel划分出了多个segment来对key-value进行存储)每次操作都只对当前segment进行锁定,从而避免每次put操作锁住整个map。

V put(K key, int hash, V value, boolean onlyIfAbsent) {lock();try {int c = count;if (c++ > threshold) // ensure capacityrehash();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;if (e != null) {oldValue = e.value;if (!onlyIfAbsent)e.value = value;}else {oldValue = null;++modCount;tab[index] = new HashEntry<K,V>(key, hash, first, value);count = c; // write-volatile}return oldValue;} finally {unlock();}}

这个方法进来就上锁(lock),并在finally中确保释放锁(unlock)。

添加key-value的过程中,先判断当前存储对象个数加1后是否大于threshold,如果大于则进行扩容(对象数组扩大两倍,进行重新hash,转移到新数组)。

如果不大于,则进行后续操作。通过对hash值和对象数组大小减1的值进行按位与操作(取余),得到当前key需要放入数组的位置,接着寻找对应位置上的hashEntry对象链表,并进行遍历。

如果找到相同key值的Entry,则替换该Entry对象的value。

如果没有找到就创建一个Entry对象,赋值给对应位置的数组对象,并构成链表。

注意:采用segment这种方式,在并发操作过程中,可以在很多程度上减少阻塞现象。

2)删除

remove(Object key)

public V remove(Object key) {int hash = hash(key.hashCode());return segmentFor(hash).remove(key, hash, null);}

和put类似,删除也要根据hash先获得segment,然后在segment上执行remove操作。

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();}}

segment的remove操作,首先加锁,然后对hash值与数组大小减1的值按位与操作,得到数组对应位置上的HashEntry对象,接下来遍历此链表,查找hash值相等并且key相等(equals)的对象。

如果没有找到,返回null,释放锁。

如果找到了,则重新创建位于删除元素之前的所有HashEntry,位于其后的不用处理。释放锁!

3)获取

get(Object key)

直接看看segment中的get操作,如下:

V get(Object key, int hash) {if (count != 0) { // read-volatileHashEntry<K,V> e = getFirst(hash);while (e != null) {if (e.hash == hash && key.equals(e.key)) {V v = e.value;if (v != null)return v;return readValueUnderLock(e); // recheck}e = e.next;}}return null;}

可以看出并没有加锁操作,只有v==null时,进入readValueUnderLock才有加锁操作。

这里假设一种情况,例如两条线程a、b,a执行get操作,b执行put操作。

当a执行到getFirst,与当前数组长度减1按位与操作后得到指定位置index,此时cpu将执行权交给b,b线程put一对key-value,导致扩容并重新hash排列,然后cpu又将执行权还给a,a然后根据之前的index去获取HashEntry就会发生问题。

当然这种情况发生的概率很小。

4)遍历

其实这个过程和读取过程类似,读取所有分段中的数据即可。

ConcurrentHashMap默认情况下采用将数据分为16个段进行存储,并且每个段各自拥有自己的锁,锁仅用于put和remove等改变集合对象的操作,基于voliate及hashEntry链表的不变性实现读取的不加锁。

这些方式使得ConcurrentHashMap能够保持极好的并发操作,尤其是对于读远比插入和删除频繁的map而言,而它采用的这些方法也可谓是对于java内存模型、并发机制深刻掌握的体现,是一个设计得非常不错的支持高并发的集合对象。

——摘自《分布式java应用》

 

补充:

正如已经存在线程安全的 List的实现,您可以用多种方法创建线程安全的、基于 hash 的 Map-- Hashtable,并使用Collections.synchronizedMap()封装 HashMap。JDK 5.0 添加了ConcurrentHashMap实现,该实现提供了相同的基本线程安全的 Map功能,但它大大提高了并发性。

Hashtable synchronizedMap所采取的获得同步的简单方法(同步 Hashtable中或者同步的 Map封装器对象中的每个方法)有两个主要的不足。首先,这种方法对于可伸缩性是一种障碍,因为一次只能有一个线程可以访问 hash 表。同时,这样仍不足以提供真正的线程安全性,许多公用的混合操作仍然需要额外的同步。虽然诸如get() put() 之类的简单操作可以在不需要额外同步的情况下安全地完成,但还是有一些公用的操作序列,例如迭代或者 put-if-absent(空则放入),需要外部的同步,以避免数据争用。

Hashtable Collections.synchronizedMap通过同步每个方法获得线程安全。这意味着当一个线程执行一个 Map方法时,无论其他线程要对 Map进行什么样操作,都不能执行,直到第一个线程结束才可以。

对比来说,ConcurrentHashMap允许多个读取几乎总是并发执行,读和写操作通常并发执行,多个同时写入经常并发执行。结果是当多个线程需要访问同一 Map时,可以获得更高的并发性。

在大多数情况下,ConcurrentHashMap HashtableCollections.synchronizedMap(new HashMap())的简单替换。然而,其中有一个显著不同,即 ConcurrentHashMap实例中的同步不锁定映射进行独占使用。实际上,没有办法锁定 ConcurrentHashMap进行独占使用,它被设计用于进行并发访问。为了使集合不被锁定进行独占使用,还提供了公用的混合操作的其他(原子)方法,如 put-if-absent。ConcurrentHashMap返回的迭代器是弱一致的,意味着它们将不抛出 ConcurrentModificationException,将进行"合理操作"来反映迭代过程中其他线程对 Map的修改。

 

 

原创粉丝点击