12、并发集合类:ConcurrentHashMap
ArrayList,HashSet,HashMap等等Java集合类
在多线程下是非线程安全的,Java集合中能做到线程安全的是vector和HashTable
ArrayList能否做到线程安全呢?
需要借助于Collections.synchronizedXXX()
借助该工具类能够将Java集合中的类封装安全使用
源码分析(JDK1.7)
继承关系
ConcurrentHashMap的继承关系如下:
通过上图可知,ConcurrentHashMap是属于ConcurrentMap接口,而ConcurrentMap接口又是Map接口的子接口,ConcurrentHashMap是实现了Map提供的所有方法
public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
implements ConcurrentMap<K, V>, Serializable
ConcurrentMap接口提供方法如下:
public interface ConcurrentMap<K, V> extends Map<K, V> {
//如果指定键已经不再与某个值相关联,则将它与给定值关联 ->key不存在该集合中,将进行put插入操作,key存在则直接返回
V putIfAbsent(K key, V value);
//只有目前将键的条目映射到给定值时,才移除该键的条目 ->key和value键值对同时存在则删除,否则不进行操作
boolean remove(Object key, Object value);
//只有目前将键的条目映射到给定值时,才替换该键的条目 ->key和oldvalue键值对存在时,才替换为key和newvalue键值对
boolean replace(K key, V oldValue, V newValue);
//只有目前将键的条目映射到某一值时,才替换该键的条目 ->key存在集合中,才进行key和value键值对的更新
V replace(K key, V value);
}
构造函数
ConcurrentHashMap提供了5个构造函数,主要的构造函数是ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel)
其中initialCapacity
叫做初始化容量,不是整个ConcurrentHashMap的容量,而是table
属性的初始数组容量,大于给定值的最小的2的幂,loadFactor
是加载因子concurrencyLevel
是并发度,指的是外层数组的大小,即segments
数组的大小,大于给定值的最小的2的幂。
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
//参数校验
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
if (concurrencyLevel > MAX_SEGMENTS)
concurrencyLevel = MAX_SEGMENTS;
// Find power-of-two sizes best matching arguments
int sshift = 0;
int ssize = 1;
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}
this.segmentShift = 32 - sshift;
this.segmentMask = ssize - 1;
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
int c = initialCapacity / ssize;
if (c * ssize < initialCapacity)
++c;
int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c)
cap <<= 1;
// create segments and segments[0]
Segment<K,V> s0 =
new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
(HashEntry<K,V>[])new HashEntry[cap]);
Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize]; /4
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
this.segments = ss;
}
内部类Segment声明
static final class Segment<K,V> extends ReentrantLock implements Serializable
Segment是继承ReentrantLock
类,该类是重入锁
属性和默认值
//默认的初始容量 默认16
static final int DEFAULT_INITIAL_CAPACITY = 16;
默认的加载因子 0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
默认的并发度
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
容量的最大值 2^30
static final int MAXIMUM_CAPACITY = 1 << 30;
最小的segment的表容量 2
static final int MIN_SEGMENT_TABLE_CAPACITY = 2;
最大的segment的容量 2^16
static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
锁重试次数2
static final int RETRIES_BEFORE_LOCK = 2;
数据存储在segment数组中
final Segment<K,V>[] segments;
static final class Segment<K,V> {
transient volatile HashEntry<K,V>[] table //数据存储在table属性中
}
class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;
}
底层的数据结构是数据+Hash表结构
ConcurrentHashMap存储数据在segments
属性上,该属性是一个Segment数组,该数组存储都是Segment类型的数据,而Segment类中存储数据在table
属性上,table属性是HashEntry
类型的数组,其数据通过数组加链表来存储数据,具体存储格式示意图如下:
常用方法的源码阅读
- put操作
public V put(K key, V value) {
Segment<K,V> s;
//校验value值不能为null,否则抛NullPointerException异常
if (value == null)
throw new NullPointerException();
// 对key进行hash
int hash = hash(key);
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
//找到key对应的segment为卡槽 拿到对应的segment的对象
s = ensureSegment(j);
return s.put(key, hash, value, false);
}
private Segment<K,V> ensureSegment(int k) {
final Segment<K,V>[] ss = this.segments;
long u = (k << SSHIFT) + SBASE; // raw offset
Segment<K,V> seg;
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
Segment<K,V> proto = ss[0]; // use segment 0 as prototype
int cap = proto.table.length;
float lf = proto.loadFactor;
int threshold = (int)(cap * lf);
HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
//当前卡槽位置的segment为null是,初始化该卡槽的数据即hashEntry实体对象
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) {
// recheck
Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) {
//乐观锁 CAS操作,提供对象的创建是原子操作
if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
break;
}
}
}
return seg;
}
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
//加锁操作
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value); //加锁失败时调用scanAndLockForPut ,尝试再次加锁
V oldValue;
try {
HashEntry<K,V>[] tab = table;
//通过key的hash来找到在table上的数组位置
int index = (tab.length - 1) & hash;
//将该位置下的第一个数据获取到
HashEntry<K,V> first = entryAt(tab, index);
for (HashEntry<K,V> e = first;;) {
//遍历hashentry链表,判断key是否存在,key存在则更新value,key不存在则新创建hashentry的实体
if (e != null) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
//key存在
oldValue = e.value;
//putIfAbsent方法 的逻辑,key存在则不更新
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
//key不存在
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;
}
}
} finally {
//释放锁的过程
unlock();
}
return oldValue;
}
//扩容操作方法
private void rehash(HashEntry<K,V> node) {
HashEntry<K,V>[] oldTable = table;
int oldCapacity = oldTable.length;
int newCapacity = oldCapacity << 1;
threshold = (int)(newCapacity * loadFactor);
HashEntry<K,V>[] newTable =
(HashEntry<K,V>[]) new HashEntry[newCapacity];
int sizeMask = newCapacity - 1;
for (int i = 0; i < oldCapacity ; i++) {
HashEntry<K,V> e = oldTable[i];
if (e != null) {
HashEntry<K,V> next = e.next;
int idx = e.hash & sizeMask;
if (next == null) // Single node on list
newTable[idx] = e;
else {
// Reuse consecutive sequence at same slot
HashEntry<K,V> lastRun = e;
int lastIdx = idx;
for (HashEntry<K,V> last = next;
last != null;
last = last.next) {
int k = last.hash & sizeMask;
if (k != lastIdx) {
lastIdx = k;
lastRun = last;
}
}
newTable[lastIdx] = lastRun;
// Clone remaining nodes
for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {
V v = p.value;
int h = p.hash;
int k = h & sizeMask;
HashEntry<K,V> n = newTable[k];
newTable[k] = new HashEntry<K,V>(h, p.key, v, n);
}
}
}
}
int nodeIndex = node.hash & sizeMask; // add the new node
node.setNext(newTable[nodeIndex]);
newTable[nodeIndex] = node;
//将扩容后的newtable进行数据填充(对原table的数据全部进行重哈希),将newtable赋值给table
table = newTable;
}
ConcurrenthashMap的特点
- ConcurrentHashMap的扩容针对是是table扩容,segment不进行扩容
- ConcurrenthashMap的扩容是原table大小的2倍
- ConcurrentHashMap中key和value都不能为空
- ConcurrentHashMap的加锁针对的是每一个Segment加锁(tryLock(), unlock())
- remove操作
public V remove(Object key) {
int hash = hash(key);
//通过hash找到该key存储在Segment上的卡槽
Segment<K,V> s = segmentForHash(hash);
return s == null ? null : s.remove(key, hash, null);
}
private Segment<K,V> segmentForHash(int h) {
long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
return (Segment<K,V>) UNSAFE.getObjectVolatile(segments, u);
}
segment上的remove操作
final V remove(Object key, int hash, Object value) {
//加锁
if (!tryLock())
scanAndLock(key, hash);
V oldValue = null;
try {
HashEntry<K,V>[] tab = table;
//通过hash操作找到对应table卡槽的第一个数据
int index = (tab.length - 1) & hash;
HashEntry<K,V> e = entryAt(tab, index);
HashEntry<K,V> pred = null;
//遍历链表,找到key,将对应key的hashentry实体从链表上删除
while (e != null) {
K k;
HashEntry<K,V> next = e.next;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
V v = e.value;
if (value == null || value == v || value.equals(v)) {
if (pred == null)
setEntryAt(tab, index, next);
else
pred.setNext(next);
++modCount;
--count;
oldValue = v;
}
break;
}
pred = e;
e = next;
}
} finally {
//释放锁
unlock();
}
return oldValue;
}
- get操作(get操作没有进行加锁)
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;
}
在源码的阅读中,我们知道remove
、put
等修改ConcurrentHashMap的结构时,需要加锁操作,加锁是采用了一种分段锁的思想,分段锁是将数据分成一段一段的存储,然后给每一段数据上进行加锁操作,当一个线程占用锁访对一段数据进行操作的时候,其他的线程是无法对同一段的数据进行操作,但是可以访问其他端的数据,这就是分段锁的思想。
分段锁的概念在JDK 1.7上就是使用该分段锁来实现的COncurrentHashMap,是使用ReentrankLock来实现
在JDK 1.8中使用的是数据+链表或红黑树来实现,volatile加Synchronized等实现的锁