zoukankan      html  css  js  c++  java
  • 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.那就说明 要么有线程在写,要不就是有线程等待要写。

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

  • 相关阅读:
    开博
    jmeter插件安装
    eclipse清理项目缓存
    java.lang.UnsupportedClassVersionError: JVMCFRE003解决方法--jdk 1.6 中switch的参数无法使用String类型
    转:Eclipse Memory Analyzer入门学习笔记
    转发: 探秘Java中的String、StringBuilder以及StringBuffer
    Java.net.SocketException: Unrecognized Windows Sockets error: 0: JVM_Bind异常
    windows 查看端口是否被占用
    iostat命令
    计算机原码、补码、反码与java移位运算符(<</>>/>>>)
  • 原文地址:https://www.cnblogs.com/juniorMa/p/13840721.html
Copyright © 2011-2022 走看看