一、简介
ConcurrentHashMap是jdk下current包中线程安全的key/value集合。这里对jdk1.7中ConcurrentHashMap的
源码进行解读。主要包含实现了ReentrantLock锁的Segment类及其数组,HashEntry类及其数组。
二、ConcrrentHashMap基本数据结构
1、jdk1.7中的ConcrrentHashMap基本数据结构为“数组+链表”。
2、为了线程安全,将数据分段加锁(分段为segment,其直接继承了ReentrantLock锁),各个分段构成
一个数组,每个分段里是HashEntry数组,每个HashEntry是数据的存储节点,同时也是链表节点。
采用分段加锁(使用ReentrantLock锁),使多个线程可同时操作ConcurrentHashMap的各个段,
ReentrantLock锁使锁操作更灵活,更高效,在加锁前,先尝试获取锁。
3、数据结构示例如下:
segment[0](ReentrantLock锁) -- HashEntry[0] --> HashEntry --> HashEntry HashEntry[1] --> HashEntry HashEntry[2] --> HashEntry --> HashEntry --> HashEntry . . segment[1](ReentrantLock锁) -- HashEntry[0] --> HashEntry --> HashEntry HashEntry[1] --> HashEntry HashEntry[2] --> HashEntry --> HashEntry --> HashEntry . . segment[2](ReentrantLock锁) -- HashEntry[0] --> HashEntry --> HashEntry HashEntry[1] --> HashEntry HashEntry[2] --> HashEntry --> HashEntry --> HashEntry . . . . .
三、源码解读
jdk1.7中ConcurrentHashMap最大的特点是采用了分段锁。
1、ConcurrentHashMapextends继承了AbstractMap抽象类,实现了ConcurrentMap接口。
源码如下:
public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>, Serializable {
2、ConcurrentHashMap是分段加锁,每个段为Segment类,其继承了ReentrantLock类,
段内包含HashEntry数组(真正存储数据元素的地方),
Segment类定义部分源码如下:
static final class Segment<K,V> extends ReentrantLock implements Serializable { /* * Segments maintain a table of entry lists that are always * kept in a consistent state, so can be read (via volatile * reads of segments and tables) without locking. This * requires replicating nodes when necessary during table * resizing, so the old lists can be traversed by readers * still using old version of table. * * This class defines only mutative methods requiring locking. * Except as noted, the methods of this class perform the * per-segment versions of ConcurrentHashMap methods. (Other * methods are integrated directly into ConcurrentHashMap * methods.) These mutative methods use a form of controlled * spinning on contention via methods scanAndLock and * scanAndLockForPut. These intersperse tryLocks with * traversals to locate nodes. The main benefit is to absorb * cache misses (which are very common for hash tables) while * obtaining locks so that traversal is faster once * acquired. We do not actually use the found nodes since they * must be re-acquired under lock anyway to ensure sequential * consistency of updates (and in any case may be undetectably * stale), but they will normally be much faster to re-locate. * Also, scanAndLockForPut speculatively creates a fresh node * to use in put if no node is found. */ private static final long serialVersionUID = 2249069246763182397L; /** * The maximum number of times to tryLock in a prescan before * possibly blocking on acquire in preparation for a locked * segment operation. On multiprocessors, using a bounded * number of retries maintains cache acquired while locating * nodes. */ static final int MAX_SCAN_RETRIES = Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1; /** * The per-segment table. Elements are accessed via * entryAt/setEntryAt providing volatile semantics. */ transient volatile HashEntry<K,V>[] table; /** * The number of elements. Accessed only either within locks * or among other volatile reads that maintain visibility. */ transient int count; /** * The total number of mutative operations in this segment. * Even though this may overflows 32 bits, it provides * sufficient accuracy for stability checks in CHM isEmpty() * and size() methods. Accessed only either within locks or * among other volatile reads that maintain visibility. */ transient int modCount; /** * The table is rehashed when its size exceeds this threshold. * (The value of this field is always <tt>(int)(capacity * * loadFactor)</tt>.) */ transient int threshold; /** * The load factor for the hash table. Even though this value * is same for all segments, it is replicated to avoid needing * links to outer object. * @serial */ final float loadFactor; Segment(float lf, int threshold, HashEntry<K,V>[] tab) { this.loadFactor = lf; this.threshold = threshold; this.table = tab; }
段内元素更新操作时,都必须先tryLock()尝试获取锁,之后才能进行更新操作,关键方法是:
scanAndLock(Object key, int hash)和scanAndLockForPut(K key, int hash, V value),
段内元素更新操作部分源码如下:
final V put(K key, int hash, V value, boolean onlyIf