一、什么是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操作始终拿到的是最新的值。