官网对ConcurrentHashMap的使用场景的介绍
//{Hashtable} is synchronized. If a
//thread-safe implementation is not needed, it is recommended to use
//{HashMap} in place of {@code Hashtable}. If a thread-safe
//highly-concurrent implementation is desired, then it is recommended
//to use {@link java.util.concurrent.ConcurrentHashMap} in place of
//{@code Hashtable}.
//不考虑并发的情况,使用HashMap()
//考虑低并发的情况,使用Hashtable()
//高并发的情况使用ConcurrentHashMap
为什么HashTable是线程安全的
HashTable能够保证线程安全的原因是,put、get、remove以及size、isEmpty等方法全部都用了Synchronized加同步锁,保证每一次只有一个线程能够对其进行改动。保证了线程安全,但是效率很低。
JDK1.7和JDK1.8的不同实现
JDK1.7里面,ConcurrentHashMap是用Segment和HashEntry实现的,每个Segment都是继承于Reentrantlock的,在对该segment进行操作时,获取锁,结束操作释放锁。
JDK1.8里面,没有用segment,而是用Node+CAS+synchronized实现的。
Node+CAS+synchronized实现
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
implements ConcurrentMap<K,V>
//重点在于Concurrent接口中,putIfAbsent,remove,replace方法的实现
//与并发有关的默认值
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
static final int NCPU = Runtime.getRuntime().availableProcessors();
//用Unsafe类用static final生成一个单例对象
private static final Unsafe theUnsafe = new Unsafe();
public static Unsafe getUnsafe() {
return theUnsafe;
}
//ConcurrentHashMap里面获取这个对象
private static final Unsafe U = Unsafe.getUnsafe();
//并调用这个对象的native方法直接操作内存,比如下面的赋值
private transient volatile int sizeCtl;
private static final long SIZECTL;
SIZECTL = U.objectFieldOffset
(ConcurrentHashMap.class, "sizeCtl");
//SIZECTL指向ConcurrentHashMap.class对象名为sizeCtl的变量的存储地址
putVal的实现如下:
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh; K fk; V fv;
//如果table为空,初始化table
if (tab == null || (n = tab.length) == 0)
tab = initTable();
//table不为空,但是tabAt(index)为空
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//直接调用CAS方法,放到桶里,expected为null
// no lock when adding to empty bin
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
break;
}
//tabAt(index)不为空,判断
else if ((fh = f.hash) == MOVED)
//static final int MOVED= -1; // hash for forwarding nodes
//如果该桶正在扩容,本线程帮助扩容
tab = helpTransfer(tab, f);
else if (onlyIfAbsent // check first node without acquiring lock
&& fh == hash
&& ((fk = f.key) == key || (fk != null && key.equals(fk)))
&& (fv = f.val) != null)
return fv;
else {
V oldVal = null;
//向链表中插入结点的过程用synchronized设定为原子方法。
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key, value);
break;
}
}
}
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
else if (f instanceof ReservationNode)
throw new IllegalStateException("Recursive update");
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
只要是对链表进行改动,get或者put都是调用的unsafe类的原子方法。
//AtomicMethod
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
return (Node<K,V>)U.getReferenceAcquire(tab, ((long)i << ASHIFT) + ABASE);
}
//底层是public native Object getReferenceVolatile(Object o, long offset);
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSetReference(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
U.putReferenceRelease(tab, ((long)i << ASHIFT) + ABASE, v);
}
java.util.concurrent包中借助CAS实现了区别于synchronouse同步锁的一种乐观锁,使用这些类在多核CPU的机器上会有比较好的性能.
CAS算法的过程是这样:它包含三个参数CAS(V,E,N)。V表示要更新的变量,E表示预期值,N表示新值。仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。