concurrentHashmap和HashMap的区别:
concurrentHashmap和HashMap大多数下的使用场景基本一致,但最大的区别就是concurrentHashmap是线程安全的HashMap不是,在并发的场景下HashMap存在死循环的问题。可以参见博客https://juejin.im/entry/5884f1a7128fe1006c3b6aac
concurrentHashmap和HashTable的区别:
HashTable是一个线程安全的容器类,在HashTable所有方法都是用synchronized关键字修饰的,也就是说它是线程安全的。但是HashTable的性能十分低下,对于每一个操作都要做加锁操作,即使操作的是不同的bucket内的Entry也要全局枷锁,在高并发场景下性能低下。而concurrentHashmap引入了分段锁的概念,对于不同Bucket的操作不需要全局锁来保证线程安全。
一、初始化及变量
对于不同的bucket用不同的锁来管理,增加了并发性。
/**
* The default initial capacity for this table,
* used when not otherwise specified in a constructor.
* 默认的初始化容量
*/
static final int DEFAULT_INITIAL_CAPACITY = 16;
/**
* The default load factor for this table, used when not
* otherwise specified in a constructor.
* 装载因子
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* The default concurrency level for this table, used when not
* otherwise specified in a constructor.
* 默认并发等级
*/
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
/**
* The maximum capacity, used if a higher value is implicitly
* specified by either of the constructors with arguments. MUST
* be a power of two <= 1<<30 to ensure that entries are indexable
* using ints.
* 最大容量
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The minimum capacity for per-segment tables. Must be a power
* of two, at least two to avoid immediate resizing on next use
* after lazy construction.
* SEGMENT的最小容量
*/
static final int MIN_SEGMENT_TABLE_CAPACITY = 2;
/**
* The maximum number of segments to allow; used to bound
* constructor arguments. Must be power of two less than 1 << 24.SEGMENT的最大容量
*/
static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
/**
* Number of unsynchronized retries in size and containsValue
* methods before resorting to locking. This is used to avoid
* unbounded retries if tables undergo continuous modification
* which would make it impossible to obtain an accurate result.
*/
static final int RETRIES_BEFORE_LOCK = 2;
二、存储结构
1 每一个segment都是一个特定的HashMap,segment的个数也就是并发数量,table的个数至少是2,而这个table中的每个元素才对应原来HashMap的桶。
2 概念
Segment : 可重入锁,继承ReentrantLock 也称之为桶
HashEntry : 主要存储键值对 可以叫节点
3 HashEntry源码
volatile保证了多线程每次读的时候可见性。ConcurrentHashMap包含一个Segment数组,每个Segment包含一个HashEntry数组,当修改HashEntry数组采用开链法处理冲突,所以它的每个HashEntry元素又是链表结构的元素。
static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;
HashEntry(int hash, K key, V value, HashEntry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
/**
* Sets next field with volatile write semantics. (See above
* about use of putOrderedObject.)
*/
final void setNext(HashEntry<K,V> n) {
UNSAFE.putOrderedObject(this, nextOffset, n);
}
// Unsafe mechanics
static final sun.misc.Unsafe UNSAFE;
static final long nextOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class k = HashEntry.class;
nextOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("next"));
} catch (Exception e) {
throw new Error(e);
}
}
}
四、构造方法
整个初始化是通过参数initialCapacity(初始容量),loadFactor(增长因子)和concurrencyLevel(并发等级)来初始化segmentShift(段偏移量)、segmentMask(段掩码)和segment数组。
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;//1 并发等级不能超过最大segments的数量
// Find power-of-two sizes best matching arguments
int sshift = 0;
int ssize = 1;
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;//2 如果你传入的是15 就是向上取2的4次方倍 也就是16
}
this.segmentShift = 32 - sshift;//3 segmentShift和segmentMask在定位segment使用
this.segmentMask = ssize - 1;//4
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;
//5 create segments and segments[0] 初始化segment
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];
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
this.segments = ss;
}
参考文献
https://juejin.im/entry/5884f1a7128fe1006c3b6aac
http://www.blogjava.net/DLevin/archive/2013/10/18/405030.html