ConcurrentHashMap使用Node+CAS+synchronized实现线程安全

官网对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值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。

原创文章 64 获赞 27 访问量 9428

猜你喜欢

转载自blog.csdn.net/weixin_44893585/article/details/104580146