ConcurrentHashMap红黑数的读写逻辑

  本文就ConcurrentHashMap红黑树的读写逻辑,尤其是jdk怎么控制读写同步的逻辑梳理下

  本文要讨论的问题主要分两种

  1 有线程在读,写进程怎么办?

  2 有线程在写,读进程怎么办?

  TreeBin的hash值  -2哦

  static final int TREEBIN   = -2; // hash for roots of trees

一  有线程在读,写进程怎么办?

  先来看看

static final class TreeBin<K,V> extends Node<K,V> {
        TreeNode<K,V> root;
        volatile TreeNode<K,V> first;
        volatile Thread waiter;
        volatile int lockState;
        // values for lockState
        static final int WRITER = 1; // set while holding write lock
        static final int WAITER = 2; // set when waiting for write lock
        static final int READER = 4; // increment value for setting read lock

  lockState至关重要,读红黑树和写红黑树都会通过CAS改lockState。

  读操作发现此时 lockState = 0;会通过cas对它加4

  再写之前会有竞争锁的操作

private final void lockRoot() {
            if (!U.compareAndSwapInt(this, LOCKSTATE, 0, WRITER))
                contendedLock(); // offload to separate method

  如果有读进程lockState肯定不是0,CAS失败,进入 contendedLock()。

private final void contendedLock() {
            boolean waiting = false;
            for (int s;;) {     //这是一个自旋
                if (((s = lockState) & ~WAITER) == 0) {//只有读线程退出了 也就是lockState = 0,该if才可能是0,所以第一次不可能进来
                    if (U.compareAndSwapInt(this, LOCKSTATE, s, WRITER)) {//
                        if (waiting)
                            waiter = null;
                        return;
                    }
                }
                else if ((s & WAITER) == 0) { //假设有一个读线程 s = 4, 0100,WAITER = 2,0010,与的结果就是0,第一次是最可能进到这个分支
                    if (U.compareAndSwapInt(this, LOCKSTATE, s, s | WAITER)) { //此时LOCKSTATE的值第二位肯定是1,因为任何一个数和 0010或,1那位结果就是1
                        waiting = true;
                        waiter = Thread.currentThread();
                    }
                }
                else if (waiting)
                    LockSupport.park(this);
            }
        }

  注意这个是一个自旋操作,自旋操作往往第一次不会有结果。

  所以,第一次自旋发现有读线程,因为 LOCKSTATE 不等于0。这时候会把第二位置为1。然后

waiting = true;
waiter = Thread.currentThread();

 然后第二次自旋,如果第二次自旋发现,读线程还是没有释放。很显然就会走到

else if (waiting)
                    LockSupport.park(this);

  写进程必须挂起。

  所以,结论就是如果有读线程,写线程必须挂起。把线程本身的引用,赋值给 TreeBin的waiter,并等待唤醒。

  现在我们看看读线程退出了,是怎么唤醒的。

final Node<K,V> find(int h, Object k) {
            if (k != null) {
                for (Node<K,V> e = first; e != null; ) {
                    int s; K ek;
                    if (((s = lockState) & (WAITER|WRITER)) != 0) {
                        if (e.hash == h &&
                            ((ek = e.key) == k || (ek != null && k.equals(ek))))
                            return e;
                        e = e.next;
                    }
                    else if (U.compareAndSwapInt(this, LOCKSTATE, s,
                                                 s + READER)) {
                        TreeNode<K,V> r, p;
                        try {
                            p = ((r = root) == null ? null :
                                 r.findTreeNode(h, k, null));
                        } finally {
                            Thread w;
                            if (U.getAndAddInt(this, LOCKSTATE, -READER) ==
                                (READER|WAITER) && (w = waiter) != null)
                                LockSupport.unpark(w);
                        }
                        return p;
                    }
                }
            }
            return null;
        }

  先不看其他的代码,看 finally 

U.getAndAddInt(this, LOCKSTATE, -READER) == (READER|WAITER)

  getAndAddInt会先得到旧值,然后再减去4,也就是 加上 -READER。

  如果这个值就是110 (100 | 10),那就说明它是最后的读线程,同时说明有等待中的写线程。

  所以执行唤醒。

  以上我们就分析完了在有读线程的情况下,写线程的处理逻辑。

二  有线程在写,读线程怎么办?

  这种情况相对要容易很多。代码就在上贴过的find

if (((s = lockState) & (WAITER|WRITER)) != 0) {
                        if (e.hash == h &&
                            ((ek = e.key) == k || (ek != null && k.equals(ek))))
                            return e;
                        e = e.next;
                    }

如果发现  (s = lockState) & (WAITER|WRITER) 不为0.那就说明 要么有线程在写,要不就是有线程等待要写。

  所以此时就退化为按照链表的方式进行查找。

posted on 2020-10-19 16:03  MaXianZhe  阅读(212)  评论(0编辑  收藏  举报

导航