Concurrent HashMap的特点:线程安全
1、Concurrent HashMap的底层数据结构?
数组(Segment)+数组(table)+链表(hashEntry)(Segment(初始化后不可扩容)+table(可扩容)+链表)
通过分析Hashtable就知道,synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的hash table,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。
2、HashMap、HashTable和ConcurrentHashMap区别是什么?
(1)安全性问题:
HashMap不能保证线程安全
HashTable能保证线程
Concurrent HashMap能保证线程安全
(2)继承关系:
HashMap继承自AbstractMap
HashTable继承自Dictionary
Concurrent HashMap继承自AbstractMap
(3)null值问题
HashMap的key和value都可以为null
HashTable的key和value都不能为null
Concurrent HashMap的key和value都不能为null
(4)扩容方式
HashMap按照2table.length
HashTable按照2table.length+1
Concurrent HashMap按照oldCapacity << 1
HashEntry[] oldTable = table;
int oldCapacity = oldTable.length;
int newCapacity = oldCapacity << 1;
(5)默认值
HashMap默认数组大小为16
HashTable默认数组大小为11
Concurrent HashMap默认数组为16
static final int DEFAULT_INITIAL_CAPACITY = 16;
(6)效率不同
HashMap在单线程小效率高
HashTable在单线程小效率低
ConcurrentHashMap在多线程共享时
(7)锁的方式
Concurrent HashMap分段锁,完全允许多个读操作并发进行,读操作并不需要加锁。
Hash Table是对整张表进行加锁
HashMap 读操作需要加锁,不然就会得到不一致的数据.
public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
implements ConcurrentMap<K, V>, Serializable
Concurrent HashMap继承自AbstractMap,实现了ConcurrentMap接口,实现了Serializable表示Concurrent HashMap可以被序列化。
// 表的最大容量
private static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认表的大小
private static final int DEFAULT_CAPACITY = 16;
// 默认并发数
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
// 装载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//默认Table数组的最小值
static final int MIN_SEGMENT_TABLE_CAPACITY = 2;
//默认segments的最大值
static final int MAX_SEGMENTS = 1 << 16;
//带参数的构造函数
// 创建一个带有默认初始容量 (16)、加载因子 (0.75) 和 concurrencyLevel (16) 的新的空映射。
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;
//计算ssize左移的次数
int ssize = 1;
//计算segment的大小
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;
//计算每个segment的大小
if (c * ssize < initialCapacity)
++c;
//若是条件成立,表示c有余数,所以增加一个segment
int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c)
cap <<= 1;
// create segments and segments[0]
Segment s0 =
new Segment(loadFactor, (int)(cap * loadFactor),
(HashEntry[])new HashEntry[cap]);
Segment[] ss = (Segment[])new Segment[ssize];
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
this.segments = ss;
}
// 创建一个带有指定初始容量、加载因子和默认 concurrencyLevel (16) 的新的空映射。
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL);
}
//创建一个带有指定初始容量、默认加载因子(0.75) 和 concurrencyLevel (16) 的新的空映射
public ConcurrentHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
public ConcurrentHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
// 构造一个与给定映射具有相同映射关系的新映射
public ConcurrentHashMap(Map extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY),
DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
putAll(m);
}
方法摘要:
void clear():从该映射中移除所有映射关系
boolean contains(Object value):一种遗留方法,测试此表中是否有一些与指定值存在映射关系的键。
boolean containsKey(Object key): 测试指定对象是否为此表中的键。
boolean containsValue(Object value):如果此映射将一个或多个键映射到指定值,则返回 true。
numeration<V> elements(): 返回此表中值的枚举。
Set<Map.Entry<K,V>> entrySet(): 返回此映射所包含的映射关系的 Set 视图。
v get(Object key): 返回指定键所映射到的值,如果此映射不包含该键的映射关系,则返回 null。
boolean isEmpty(): 如果此映射不包含键-值映射关系,则返回 true。
Enumeration<K> keys(): 返回此表中键的枚举。
Set<K> keySet(): 返回此映射中包含的键的 Set 视图。
V put(K key, V value): 将指定键映射到此表中的指定值。
void putAll(Map<? extends K,? extends V> m): 将指定映射中所有映射关系复制到此映射中。
V putIfAbsent(K key,V value):如果指定键已经不再与某个值相关联,则将它与给定值关联。
V remove(Object key): 从此映射中移除键(及其相应的值)。
boolean remove(Object key, Object value): 只有目前将键的条目映射到给定值时,才移除该键的条目。
V replace(K key, V value): 只有目前将键的条目映射到某一值时,才替换该键的条目。
boolean replace(K key, V oldValue, V newValue): 只有目前将键的条目映射到给定值时,才替换该键的条目。
int size(): 返回此映射中的键-值映射关系数。
Collection<V> values(): 返回此映射中包含的值的 Collection 视图
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
//每个segment操作进行put操作时都需要进行加锁操作
HashEntry node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
//尝试获取锁
V oldValue;
try {
HashEntry[] tab = table;
//table为segment连接的HashEntry数组
int index = (tab.length - 1) & hash;
//定位索引位置
HashEntry first = entryAt(tab, index);
//找到数组对应下标的链表
for (HashEntry e = first;;) {
if (e != null) {
//头结点不为空
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
//当k相同时就将oldValue替换
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
if (node != null)
node.setNext(first);
//添加新节点,添加在头部
else
node = new HashEntry(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;
}
final V remove(Object key, int hash, Object value) {
if (!tryLock())
//获取锁
scanAndLock(key, hash);
V oldValue = null;
try {
HashEntry<K,V>[] tab = table;
//table是segment所连接的hashEntrt数组
int index = (tab.length - 1) & hash;
//定位索引位置
HashEntry<K,V> e = entryAt(tab, index);
//找到数组对应下标的链表头
HashEntry<K,V> pred = null;
//
while (e != null) {
//头结点不为空
K k;
HashEntry<K,V> next = e.next;
//继续向下hash
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
//如果k的值相同且是索引位置的链表头,就将e的值赋值给v
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;
}
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;
}
3、通过什么保证线程安全?
ReentrantLock.trylock()----->重入锁
Concurrent HashMap定义了三个原子操作,用于对指定位置的节点进行操作,在put()方法中使用;
4、HashTable和concurrentHashMap线程安全保证机制是否一样?
HashTable是通过synchronized来保证线程安全的,所有的线程要竞争同一把锁,也就是说:当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞状态,导致效率降低;
Concurrent HashMap是其分为不同的数据段,每个数据段都拥有一把锁,当多线程访问不同的数据段时,就不会发生线程竞争的情况,也就是说:当一个线程访问一个数据段的时候,其他线程可以访问其他数据段,不必抢夺,会优先的提高并发访问效率;