看ConcurrentHashMap的,一般都看过HashMap的了,那么我们就直奔主题吧。
Java集合---ConcurrentHashMap原理分析
/** * The default concurrency level for this table, used when not * otherwise specified in a constructor. */ static final int DEFAULT_CONCURRENCY_LEVEL = 16; ...... public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel)
直接来到这个构造方法,可以看到,默认的concurrencyLevel为16,可以理解为分16个segment,操作时以segment为粒度大小加锁。
在HashMap基础上增加了:
1、计算segmentShift、segmentMask(用途下面会提到)
2、计算cap,实例化ss(segment的数组)、s0(第一个segment)(用途下面会提到)
int sshift = 0; int ssize = 1; while (ssize < concurrencyLevel) { ++sshift; ssize <<= 1; } this.segmentShift = 32 - sshift; this.segmentMask = ssize - 1;
这段代码为了计算segmentShift(如需加入某元素,将它分配到某个segment需移动的位数)、segmentMask(此掩码用于计算分配到哪个segment)
concurrencyLevel可以看为分多少个segment,即segment[]的长度
segmentMask这个掩码后面被用来跟hash值的高位做&操作,从而确定分配到哪个segment;
sshift为segmentMask二进制的位数,用来求segmentShift;
而segmentShift后面用来为hash值右移的位数,从而获取hash的高多少位来做&运算。
这段代码简单打印各个值,便于理解:
public class SegmentShiftMask { public static void main(String[] args) { for (int i = 1; i < 20; i++) { test(i); } } public static void test(int concurrencyLevel) { int sshift = 0; int ssize = 1; while (ssize < concurrencyLevel) { ++sshift; ssize <<= 1; } int segmentShift = 32 - sshift; int segmentMask = ssize - 1; System.out.println("concurrencyLevel : " + concurrencyLevel + " \t= " + String.format("%08d", Integer.valueOf(Integer.toBinaryString(concurrencyLevel)))); System.out.println("sshift : " + sshift); System.out.println("segmentShift : " + segmentShift); System.out.println("segmentMask : " + segmentMask + " \t= " + String.format("%08d", Integer.valueOf(Integer.toBinaryString(segmentMask)))); System.out.println("--------"); } }
打印结果:
concurrencyLevel : 15 = 00001111 sshift : 4 segmentShift : 28 segmentMask : 15 = 00001111 -------- concurrencyLevel : 16 = 00010000 sshift : 4 segmentShift : 28 segmentMask : 15 = 00001111 -------- concurrencyLevel : 17 = 00010001 sshift : 5 segmentShift : 27 segmentMask : 31 = 00011111 -------- concurrencyLevel : 18 = 00010010 sshift : 5 segmentShift : 27 segmentMask : 31 = 00011111 -------- concurrencyLevel : 19 = 00010011 sshift : 5 segmentShift : 27 segmentMask : 31 = 00011111 --------
int c = initialCapacity / ssize; if (c * ssize < initialCapacity) ++c; int cap = MIN_SEGMENT_TABLE_CAPACITY; while (cap < c) cap <<= 1;
这段是求cap(segment中包含的table数组的大小),这个cap的大小是2的N次方。
int hash = hash(key); int j = (hash >>> segmentShift) & segmentMask;
计算key的哈希值,右移segmentShift取哈希值的高位,再与掩码做&运算,得到j就是该把插入的元素放在segment[]的哪个位置了。
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment s = ensureSegment(j);
获取segment。
return s.put(key, hash, value, false); ...... static final class Segment<K,V> extends ReentrantLock implements Serializable { ...... final V put(K key, int hash, V value, boolean onlyIfAbsent) { HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(key, hash, value); V oldValue; try { ...... } finally { unlock(); } return oldValue; } ...... }
Segment以ReentrantLock方式加锁,保证线程安全,且加锁的范围在Segment,不像HashTable整体加锁了。
scanAndLockForPut()中tryLock()尝试非阻塞方式获取锁并计算尝试次数,到一定次数后在使用中lock()阻塞方式获取锁。
HashEntry<K,V>[] tab = table; int index = (tab.length - 1) & hash; HashEntry<K,V> first = entryAt(tab, index); for (HashEntry<K,V> e = first;;) { if (e != null) { K k; if ((k = e.key) == key || (e.hash == hash && key.equals(k))) { oldValue = e.value; if (!onlyIfAbsent) { e.value = value; ++modCount; } break; } e = e.next; } else { if (node != null) node.setNext(first); else node = new HashEntry<K,V>(hash, key, value, first); int c = count + 1; if (c > threshold && tab.length < MAXIMUM_CAPACITY) rehash(node); else setEntryAt(tab, index, node); ++modCount; count = c; oldValue = null; break; } } ...... static final class HashEntry<K,V> { final int hash; final K key; volatile V value; volatile HashEntry<K,V> next; ......
hash与tab.length做&操作,获取该放在哪个下标指向。覆盖数据、新增数据的逻辑与HashMap类似。
本文并未完整地描述ConcurrentHashMap的整体概括,只记录笔者浏览的部分笔记。
部分图纸笔记: