一、什么是ConcurrentHashMap?
ConcurrentHashMap是一种线程安全且高效的HashMap。
二、为什么要引入ConcurrentHashMap?
针对Key-Value类型的集合而言,HashMap不是线程安全的,无法在多线程或高并发情况下使用,而Hashtable虽然使用synchronized关键字来保证安全,但是在高并发等线程竞争比较激烈的情况下其效率非常低下。更可怕的是,使用synchronized关键字意味着访问线程不得不竞争同一把锁,不仅不允许其他线程进行put操作,也导致了get操作的阻塞,使得这种竞争越来越激烈。
正是由于以上原理,Java才不得不引入了ConcurrentHashMap。
三、ConcurrentHashMap是如何实现的?
ConcurrentHashMap使用了锁分段技术,它将数据分成一段一段的进行存储,每一段数据被单独分配一把锁。这样,当某一个线程访问某一段数据时,并不影响其他线程访问其他段数据,原因则是基于不同段数据被分配的不同的锁,线程访问不同段数据时并没有锁的竞争,从而可以提高实现高并发的效率。
ConcurrentHashMap的get()方法十分简单和高效。它先通过hash()方法得到key的hash值h,然后根据h的哈希算法得到u,即定位到segment,之后再对u进行一定的运算定位到元素,全程无锁,代码如下:
/**
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
*
* <p>More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that {@code key.equals(k)},
* then this method returns {@code v}; otherwise it returns
* {@code null}. (There can be at most one such mapping.)
*
* @throws NullPointerException if the specified key is null
*/
public V get(Object key) {
Segment<K,V> s; // manually integrate access methods to reduce overhead
HashEntry<K,V>[] tab;
int h = hash(key);
long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
(tab = s.table) != null) {
for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
e != null; e = e.next) {
K k;
if ((k = e.key) == key || (e.hash == h && key.equals(k)))
return e.value;
}
}
return null;
}
那么它是如何实现全程无锁的呢?关键就在于get()方法使用的共享变量均被定义成volatile。而被定义成volatile的变量,能够在线程之间保持可见性,能够被多线程同时读,并能保证不会读取到过期的值,但是只能被单线程写。之所以可以被多线程同时读且不会读取到过期的值,是根据java内存模型的happen before原则,对volatile字段的写入操作优先于读操作。比如,两个线程分别修改和获取volatile变量,get操作始终拿到的是最新的值。