源码剖析ConcurrentHashMap及ConcurrentHashMap和HashMap、HashTable的区别
一、ConcurrentHashMap
jdk1.7源码分析ConcurrentHashMap:(在源码中可以看到自jdk1.5开始引入ConcurrentHashMap)
ConcurrentHashMap是对HashMap线程不安全的优化,使其线程安全且高效。下面对源码的一些内容做以分析:
1、继承关系
(jdk1.5开始)继承了AbstractMap、实现了ConcurrentMap和序列化接口
public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
implements ConcurrentMap<K, V>, Serializable
2、底层数据结构
数组+数组+链表
ConcurrentHashMap源码中包含三个主要的内部类:
- Holder
- HashEntry
- Segment
在Holder类的属性中:有一个Segment
final Segment<K,V>[] segments;
HashEntry实现了ConcurrentHashMap底层的链表结构
Segment类中定义了hashEntry类型的table数组
基于上面的表述:就可以将ConcurrentHashMap的具体结构画出来
3、基本属性和默认值
默认初始容量:作用于hashEntry的table属性
static final int DEFAULT_INITIAL_CAPACITY = 16;
默认加载因子:作用于HashEntry
static final float DEFAULT_LOAD_FACTOR = 0.75f;
默认并发度:作用于Segment数组大小
(并发度:能同时操作集合的线程最大数量)static final int DEFAULT_CONCURRENCY_LEVEL = 16;
最大容量:作用于segment中数组的最大容量 table.length < MAXIMUM_CAPACITY
static final int MAXIMUM_CAPACITY = 1 << 30;
每个分段的最小容量: 作用于hashEntry类型的table数组
static final int MIN_SEGMENT_TABLE_CAPACITY = 2;
分段最大容量(最大并发度)
static final int MAX_SEGMENTS = 1 << 16;
默认自旋次数(超过这个次数直接加锁)
static final int RETRIES_BEFORE_LOCK = 2;
4、构造函数(5个)
指定初始容量16、加载因子0.75和并发度16
public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel)
指定初始容量、加载因子0.75
public ConcurrentHashMap(int initialCapacity, float loadFactor)
指定初始容量 :创建一个带有指定初始容量、默认加载因子 (0.75) 和并发度 (16) 的新的空映射
public ConcurrentHashMap(int initialCapacity)
无参构造: 创建一个带有默认初始容量 (16)、加载因子 (0.75) 和 concurrencyLevel (16) 的新的空映射。
public ConcurrentHashMap()
通过Map转化:构造一个与给定映射具有相同映射关系的新映射
public ConcurrentHashMap(Map<? extends K, ? extends V> m)
5、扩容
扩容只针对ConcurrentHashMap中的table数组进行扩容。
方法在Segment类中:rehash() , 扩容倍数为:2倍扩容
6、常用方法
- boolean isEmpty() //判断集合是否为空
- int size() //返回当前集合的键值对的个数
- V get(Object key) //通过当前的key来获取value
- boolean containsKey(Object key) //判断当前集合是否包含key
- boolean containsValue(Object value) //判断当前集合是否包含value
- V put(K key, V value) //添加元素
- V remove(Object key) //删除元素
- boolean remove(Object key, Object value) //删除元素
7、put()方法研究
ConcurrentHashMap的put()方法
public V put(K key, V value) {
Segment<K,V> s;
if (value == null) //key和value不能为null
throw new NullPointerException();
int hash = hash(key); //通过key值进行哈希
int j = (hash >>> segmentShift) & segmentMask;
//找到key对应的segment数组的索引位置,
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
s = ensureSegment(j);
//segment内部的put操作
return s.put(key, hash, value, false);
}
↓调用segment类中的put()方法
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
//先尝试性加锁,未获取到则进行重试加锁机制,(到达重试的上限,则直接阻塞一直到获取到锁)
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
V oldValue;
try {
HashEntry<K,V>[] tab = table;
//hash到数组中对应的位置
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;
//相等情况下,onlyIfAbsent默认false,当key相等时,返回旧的value的值,并将旧value值替换成新的value, 当onlyIfAbsent为true时,直接返回旧的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;
}
}
} finally {
unlock(); //释放锁
}
return oldValue;
}
二、ConcurrentHashMap和HashMap、HashTable的区别
1、hashtable和concurrentHashMap线程安全保证机制是否一样?
!!!不一样
- HashTable保证线程安全的机制:
HashTable中的方法添加有Syncnized关键字,该关键字是一个互斥锁。目的是同一时刻只能有一个线程对资源进行访问。
在HashTable中,对put,get等一般方法添加Syncnized关键字,修饰的是类对象,该对象调用put操作,即为该对象添加了一个互斥锁,同时只能有一个线程访问hashtable,从而保证添加元素不会出现异常 - ConcurrentHashMap保证线程安全的机制:
ConcurrentHashMap是将整个集合细分为多个Segment保存在segments[]数组中,Segment继承了ReentrantLock锁,就拥有了它的锁机制,就表示只有在同一个Segment内的线程在属于竞争关系,而在不同的Segment之间的线程,不会存在竞争关系。
Segment内部有一个HashEntry类型的table数组,数组中hash到同一个位置的元素会通过链表将数据加在链表后,table[]数组用volatile关键字修饰,可以保证在table数组中同一时刻只能有一个线程访问数组,
2、HashMap和HashTable和concurrentHashMap异同点?
相同点:
1.底层数据结构相同,都为数组+链表( ConcurrentHashMap为数组+数组+链表)
2.key都不能重复
3.插入元素不能保证有序
4.都是通过key进行哈希
不同点:
1.安全性问题
HashMap是非线程安全的集合,若想让其在多线程下具有安全性:使用集合工具类collection.SyncnizedMap;
HashTable中的方法都有Synchronized修饰,多线程访问时线程安全;
ConcurrentHashMap中通过分段锁机制保证线程安全
2.继承关系:
hashMap和ConcurrentHashMap继承AbstractMap
hashTable继承Dictionary
3.扩容方式:
HashMap和 ConcurrentHashMap为2倍(2table.length)
HashTable为2的倍数+1 [(2table.length)+1]
4.null值:
HashTable的键值不能为null
hashMap键值可以为null
ConcurrentHashMap键可以为null,值不能为null。
5.默认值:
hashTable数组默认大小11
hashMap数组默认大小16
ConcurrentHashMap数组默认大小16
6.hash算法不同:
7.效率不同:
hashMap在单线程下效率高
hashTable和ConcurrentHashMap在多线程下效率高(ConcurrentHashMap更高)
还没有评论,来说两句吧...