CourrentHashMap的构造方法以及put函数和红黑树

本文深入剖析了ConcurrentHashMap的内部实现,包括构造方法、初始化过程、容量调整策略、并发更新线程数、以及在不同版本中的修正。详细讲解了put操作的流程,涉及CAS插入、扩容机制、链表与红黑树转换,并分析了红黑树的平衡操作。此外,还介绍了TreeBin的锁机制,展示了读写锁的实现方式。
摘要由CSDN通过智能技术生成

构造方法

总共有五种构造器,总结: 两个重要参数 initalCapacity 初始化容量;loadFactor 扩容的负载因子(用于计算扩容阈值);默认初始化容量为16;默认负载因子为0.75。
一个BUG:一个参数构造器和三参构造器构造的集合对象长度不一致;一个参数的构造器即使在构造时,给定初始化容量是2的n次幂,结果也会被改变;在JDK12中得以修正。

/**
 * initialCapacity 集合数组初始化容量
 * loadFactor 负载因子(用于计算扩容阈值)
 * concurrencyLevel 估计的并发更新线程数。实现可以使用这个值作为大小提示
 */
public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
        /**
         * 其实就一句话,负载因子必须是大于 0 的数字;初始化容量不能 < 0; 预计并发更新线程数必须 > 0
         */
        if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
                throw new IllegalArgumentException();
        // 如果初始化容量小于预计更新线程数,初始化容量就时更新线程数
        if (initialCapacity < concurrencyLevel)
                initialCapacity = concurrencyLevel;
        // 预计初始化集合的大小;1.0 保证不会等于0(因为初始化容量可以为0)
        long size = (long)(1.0 + (long)initialCapacity / loadFactor);
        /**
         *  真正集合大小的容量,分为两种情况:
         * 1、预计容量 >= 数组最大容量,采用数组最大容量(1 << 30;大约十亿多)
         * 2、预计容量 < 数组最大容量,采用最紧急size的2的n次幂
         */
        int cap = (size >= (long)MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : tableSizeFor((int)size);
        // 将数组真正的初始化容量赋值给成员变量 sizeCtl
        this.sizeCtl = cap;
}

// 本质调用三参构造器
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
        this(initialCapacity, loadFactor, 1);
}

/**
 * BUG(两参本质调用三参)三个参数的构造器创建的对象的初始化数组长度不一致
 *
 * 正确的应该是 JDK12中修正如下:
 * public ConcurrentHashMap() {
 *      this(initialCapacity, LOAD_FACTOR, 1);
 * }
 *
 * 举例 16 负载因子采用和默认一致的 0.75
 * tableSizeFor(16 + (16 >>> 1) + 1) = 32 ; 16 + (16 >>> 1) + 1 =25 ,最接近25的2^n幂的结果就是32
 * 一个参数 cap = (16 >= (1 << 30) ? 1 << 30 : 32) = 32;
 *
 * size = 1 + 16/0.75 = 13;
 * tableSizeFor(13) =16 ,最接近13的2^n幂的结果就是16
 * 三个参数 cap = (13 >= 1 << 30 ? 1 << 30 : 16) = 16;
 *
 * 实际应该是16
 */
public ConcurrentHashMap(int initialCapacity) {
        if (initialCapacity < 0)
                throw new IllegalArgumentException();

        int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                   MAXIMUM_CAPACITY : tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
        this.sizeCtl = cap;
}


// 参数为Map对象,采用默认初始化容量,不足时再扩容
public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
        // 默认初始化容量 16
        this.sizeCtl = DEFAULT_CAPACITY;
        // 容量不足时,会在putAll 中执行 tryPreSize 尝试扩容
        putAll(m);
}

// 无参构造器 什么都没做
public ConcurrentHashMap() {
}

put方法

put方法的执行过程
1、判断key,value值是否为空,为空抛出空指针异常;
2、判断集合数组是否初始化,未初始化进行初始化;
3、用key的hash值与数组长度做 & 运算得出索引位置:hash & (n -1),并判断索引位置的节点是否为空,为空采用CAS放入数据
4、索引位置不为空分为三种情况
4.1 数组正在扩容,数据在迁移中,索引位置的节点为forward节点,forward的节点特点hash值为-1,当前线程进行协助扩容

----- 4.2和4.3前置:所以位置不管是链表还是红黑树,都先加同步锁synchronized
4.2 索引位置为链表,遍历链表,查看链表是否有相同的key存在,
– 有相同的key根据onlyIfAbsent的值进行覆盖或者保持不变;
– 没有相同的key,找到链表尾部插入数据。
4.3 索引位置为红黑树,调用TreeBin.putTreeNode插入红黑树。
4.5 如果链表长度超过8,尝试树化。(不一定会转为红黑树,转为红黑树还有一个条件,数组长度 > 64)

注意:在ConcurrentHashMap中,hash值负数有特殊含义,与HashMap不同的地方在于计算hash值时,hash & HASH_BITS,保证正常的hash值是一个正数;
HASH_BITS = 0x7fffffff;
HASH_BITS = 011111111 11111111 11111111 11111;最高位为0,保证与hash值做 & 运算后,得到一个最高位为0的值。
当Hash值负数的三种情况:

当前位置正在进行扩容
static final int MOVED = -1;

当前位置下是一棵红黑树
static final int TREEBIN = -2;

预留当前位置 ====> computIfAbsent ====> new ReservationNode<K,V>();
static final int RESERVED = -3;

static final int spread(int h) {
        return (h ^ (h >>> 16)) & HASH_BITS;
}

public V put(K key, V value) {
        /**
         * onlyIfAbsent
         * 值为false时,key存在覆盖value,key不存在,正常插入;
         * 值true时,key存在,什么的都不做,value保持原来的不变,key不存在,正常插入数据。
         */
        return putVal(key, value, false);
}


/**
 * 1、保证插入key,value都不为空,为空抛出异常直接结束方法
 * 2、判断数组是否初始化,没有初始化,初始化数组
 * 3、数组已经初始化,根据hash & n - 1 计算出在数组中的插入位置,并判断当前位置是否为null,为null,直接放入数组
 * 4、当前位置不为空,查看节点是否是正在扩容的节点 fwd( hash = -1,nextTable),如果是扩容节点,则协助扩容
 * 5、当前位置不为空,且当前节点不是扩容节点,给当前位置的节点加锁
 * 5.1 当前位置下是链表,两种情况,hash >= 0
 * -----链表中存在相同的key,根据onlyIfAbsent 判断是(false)覆盖原有value,(true)还是保持原有value不变
 * -----链表中没有相同的key,找到链表尾部插入数据
 * 5.2 hash == -2;说明当位置中是一颗红黑树
 *
 */
final V putVal(K key, V value, boolean onlyIfAbsent) {
        // key和value都不允许为空,跟HashMap的区别
        if (key == null || value == null) throw new NullPointerException();
        /**
         * 计算hash值
         */
        int hash = spread(key.hashCode());
        int binCount = 0;

        for (Node<K, V>[] tab = table;;) {
                /**
                 * f 当前索引位置的节点数据
                 * n 当前数组长度
                 * i 当前要插入数据的索引位置
                 * fh 当前索引位置i上的节点的hash值
                 */
                Node<K, V> f; int n, i, fh;
                // 判断数组是否初始化
                if (tab == null || (n = tab.length) == 0)
                        // 没有初始化,初始化数组
                        tab = initTable();
                /**
                 *  判断当前要插入数据的索引位置是否为空
                 * tabAt 在内存根据偏移量取出数据并使用volatile加载,保证可见性
                 */
                else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                        // 索引位置为空,则通过CAS插入数据
                        if (casTabAt(tab, i, null, new Node<K, V>(hash, key, value, null)))
                                // 插入成功,跳出当前循环
                                break;
                }
                // 当前位置是否正在扩容
                else if ((fh = f.hash) == MOVED)
                        // 当前插入数据的线程协助扩容
                        tab = helpTransfer(tab, f);
                // 当前索引位置下有数据,分为两种情况,当前索引位置下是链表或者一颗红黑树
                else {
                        // 链表中当前节点的value
                        V oldVal = null;
                        // 基于当前索引位置的节点数据进行加锁,也就是常说的桶锁。
                        synchronized (f) {
                                // 再次判断当前位置引用是否变更,引用变更说明发生了并发冲突
                                if (tabAt(tab, i) == f) {
                                        //当前位置中是一个链表
                                        if (fh >= 0) {
                                                // 在链表中位置的索引,1表示链表第二个位置
                                                //(数组索引为i的节点为链表首节点 ==> 0)
                                                binCount = 1;
                                                for (Node<K, V> e = f;; ++binCount) {
                                                        // 当前位置i下的链表位置为binCount位置的节点的key值。
                                                        K ek;
                                                        /**
                                                         * 判断链表中是否有相同的key,
                                                         * hash值相等,且引用地址相等 或 equals相等
                                                         */
                                                        if (e.hash == hash &&
                                                                        ((ek = e.key) == key ||
                                                                         (ek != null && key.equals(ek)))) {
                                                                // 将链表当前节点的value赋值给oldValue
                                                                oldVal = e.val;
                                                                /**
                                                                 * 是否覆盖当前链表当前位置的value属性值
                                                                 * onlyIfAbsent
                                                                 * 值为false时,key存在覆盖value,key不存在,正常插入;
                                                                 * 值true时,key存在,什么的都不做,value保持原来的不变
                                                                 * key不存在,正常插入数据。
                                                                 */
                                                                if (!onlyIfAbsent)
                                                                        e.val = value;
                                                                break;
                                                        }

                                                        Node<K, V> pred = e;
                                                        // 找到链表尾部(链表尾部的next为空)
                                                        if ((e = e.next) == null) {
                                                                /**
                                                                 * 根据当前插入key,value创建节点,
                                                                 * 并将新节点追加到链表尾部
                                                                 */
                                                                pred.next = new Node<K, V>(hash, key, value, null);
                                                                break;
                                                        }
                                                }
                                        }
                                        // 当前 i = hash & n -1 位置下是一颗红黑树
                                        else if (f instanceof TreeBin) {
                                                // 临时节点:用于接收插入红黑树后的返回值
                                                Node<K, V> p;
                                                binCount = 2; 
                                                // 插入到当前节点下红黑树中(因为当前节点是红黑树节点,所以可以强转)
                                                // p 不为空,说明红黑树中有相同的key存在
                                                if ((p = ((TreeBin<K, V>)f).putTreeVal(hash, key, value)) != null) {
                                                        // 获取要覆盖节点的原来的value
                                                        oldVal = p.val;
                                                        // 是否允许覆盖旧值
                                                        if (!onlyIfAbsent)
                                                                // 覆盖旧值
                                                                p.val = value;
                                                }
                                        }
                                }
                        }
                        if (binCount != 0) {
                                if (binCount >= TREEIFY_THRESHOLD)
                                        // 树化
                                        treeifyBin(tab, i);
                                if (oldVal != null)
                                        return oldVal;
                                break;
                        }
                }
        }
        addCount(1L, binCount);
        return null;
}
初始化集合数组-initTable

初始化数组的执行过程
1、while循环判断数组是否初始化,为空或者数组长度为空0,表示数组没有初始化
1.1、已经初始化直接返回当前数组
1.2、没有初始化,分为以下两种情况:
----- sizeCtl < 0,当前线程让步,进入就绪状态。
----- 否则,尝试设置 sizeCtl 为 -1,设置不成功继续while循环,设置成功,再次判断是数组引用是否变更,是否初始化。没有初始化进行初始化,并将sizeCtl设置为下次扩容的阈值。

sizeCtl > 0 表示数组的初始化长度或者扩容的阈值
sizeCtl = 0 表示是数组没有初始化
sizeCtl = -1 表示数组正在初始化
sizeCtl < -1 表示数组正在扩容的线程数,-2代表有一个线程扩容,以此类推


/**
 * 初始化数组
 *
 * ----- 如果是用空构造器创建的对象,sizeCtl = 0;
 * ----- 如果是用有参的构造器创建的对象,sizeCtl = 数组初始化长度(map为参数的,采用的是数组默认长度)
 *
 * 1、判断是否已经初始化
 * 2、如果没有初始化,判断是否正在初始化,正在初始化时,改变当前线程状态 running --> runnable, ?
 * 3、没有初始化,且不在初始化中,CAS 设置sizeCtl为-1,尝试初始化
 * 4、CAS 成功后再次判断数组是否初始化,没有初始化进行初始化,初始化完成后,将sizeCtl 设置为下次扩容的阈值
 */
private final Node<K, V>[] initTable() {
    // tab 当前数组,sc 当前sizeCtl的值
    Node<K, V>[] tab; int sc;
    // 再次判断数组是否初始化
    while ((tab = table) == null || tab.length == 0) {
        /**
         *  sizeCtl 数组初始化和扩容操作时的变量
         * 值为-1,当前数组正在初始化
         * 小于-1,当前数组正在扩容的线程个数(-2一个线程正在扩容,-3 两个线程正在扩容...)
         * 值为0,没有初始化
         * 值大于0,代表当前数组的扩容阈值,或者当前数组初始化大小
         *
         */
        // 判断数组是否正在初始化或者扩容
        if ((sc = sizeCtl) < 0)
            // 当前线程让步(让出CPU执行),进入就绪状态
            Thread.yield(); // lost initialization race; just spin
        // 线程以CAS的方式,设置sc的值为-1,只有一个线程能设置成功
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
            try {
                // 再次判断数组是否初始化
                if ((tab = table) == null || tab.length == 0) {
                    // 如果sc > 0 ,那么sc是数组初始化长度,否则数组长度为默认数组初始化长度==》16。
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    // 真正开始初始化数组
                    Node<K, V>[] nt = (Node<K, V>[])new Node<?, ?>[n];
                    // 将初始化后的数组赋值给局部变量tab和全局变量table
                    table = tab = nt;
                    // 下次数组扩容的阈值 3/4 = 0.75
                    sc = n - (n >>> 2);
                }
            } finally {
                // 将sc (扩容阈值)赋值给全局变量sizeCtl
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}

插入红黑树
/**
 *  插入数据到红黑树
 *
 *  与链表转红黑树类似
 */
final TreeNode<K, V> putTreeVal(int h, K k, V v) {
    // key 的类对象
    Class<?> kc = null;
    // 是否已经搜索过子节点
    boolean searched = false;
    // p 父节点的临时变量(for循环的起点是根节点)
    for (TreeNode<K, V> p = root;;) {
        /**
         * dir 判断当前节点应该插入左子树还是右子树;左:dir = -1;右:dir=1;
         * ph 父节点的hash值
         * pk 父节点的key值
         *
         */
        int dir, ph; K pk;
        // 如果父节点为空,说明树不存在
        if (p == null) {
            /**
             * 以当前插入节点为红黑树的根节点
             * 以当前插入节点为红黑树中链表的头节点
             * 结束循环(说明目前只有当前插入节点一个节点,没必要循环了)
             */
            first = root = new TreeNode<K, V>(h, k, v, null, null);
            break;
            // 如果父节点不为空,判断父节点hash 是否大于 当前插入节点的hash值
        } else if ((ph = p.hash) > h)
            // 如果父hash > hash,当前要插入的节点应该插入左子树
            dir = -1;
        else if (ph < h)
            // 如果父hash < hash,当前要插入的节点应该插入右子树
            dir = 1;
        // 如果当前插入key 与 父节点key引用地址相同 或者 key equals相等
        else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
            // 说父节点的key与当前插入key相同,直接返回当前节点
            return p;
        /**
         * 如果引用地址不等且equals不等,key实现了Comparable,且使用compareTo进行比较 k 与 pk相等时
         * 搜索子节点
         * kc = comparableClassFor(k) key的类是否实现了Comparable,实现了返回key的Class对象,否则为空
         */
        else if ((kc == null && (kc = comparableClassFor(k)) == null) ||
                 (dir = compareComparables(kc, k, pk)) == 0) {
            // 只会搜索一次,搜索一次后 searched被赋值为true,表示搜索过了
            if (!searched) {
                TreeNode<K, V> q, ch;
                searched = true;
                /**
                 * 搜索子树,如果子树中有相同的key,结束方法,并返回相同key的节点
                 * 结束方法,并返回相同key的节点
                 *
                 * ((ch = p.left) != null && (q = ch.findTreeNode(h, k, kc)) != null)
                 * 左子树不为空,搜索左子树找到key相同的节点返回节点,否则返回null
                 *
                 * ((ch = p.right) != null &&(q = ch.findTreeNode(h, k, kc)) != null)
                 * 右子树不为空,搜索右子树找到key相同的节点返回节点,否则返回null
                 */

                if (((ch = p.left) != null && (q = ch.findTreeNode(h, k, kc)) != null) ||
                        ((ch = p.right) != null && (q = ch.findTreeNode(h, k, kc)) != null))
                    return q;
            }
            // k hash值 <= pk的hash值 ? -1 : 1
            dir = tieBreakOrder(k, pk);
        }
        // 根据dir,判断放入左或右子树,并维护链表
        TreeNode<K, V> xp = p;
        if ((p = (dir <= 0) ? p.left : p.right) == null) {
            // 将根节点赋值给临时变量f;x 当前节点的临时变量
            TreeNode<K, V> x, f = first;
            // 将当前节点赋值给链表头节点
            first = x = new TreeNode<K, V>(h, k, v, f, xp);
            // 如果根节点不为空,将当前节点赋值给根节点的prev(链表的上一个节点)
            if (f != null)
                f.prev = x;
            // 将当前节点放入父节点的左子树
            if (dir <= 0)
                xp.left = x;
            // 将当前节点放入父节点的左子树
            else
                xp.right = x;
            // 如果父节点不是红色,则当前节点为红色
            if (!xp.red)
                x.red = true;
            // 如果父节点是红色,则加锁,进行自平衡
            else {
                lockRoot();
                try {
                    root = balanceInsertion(root, x);
                } finally {
                    unlockRoot();
                }
            }
            break;
        }
    }
    // 检查红黑树
    assert checkInvariants(root);
    return null;
}

维护红黑树平衡及特性

  /**
     * 红黑树做自平衡,以及保证红黑树的特性的操作。红黑树的特性;
     * ps:为了方便说明,将特性给予编号:
     * -- 1、每个节点非红即黑。
     * -- 2、根节点时黑色的。
     * -- 3、如果当前节点是红色的,它的子节点一定是黑色的
     * -- 4、叶子节点也一定是黑色的(?)
     * -- 5、从根节点出发到叶子节点路径中,黑节点数量是相同的
     *
     *
     * root:当前做自平衡操作时的根节点,x:当前节点
     */
    static <K, V> TreeNode<K, V> balanceInsertion(TreeNode<K, V> root,
            TreeNode<K, V> x) {
        // 默认插入节点时红色的
        x.red = true;
        /**
         * 这一部分挺好玩的 ;x:当前节点 p父节点
         *
         * xp:当前节点的父节点
         * xpp:爷爷节点(当前节点的父节点的父节点 ps:爸爸的爸爸叫什么?)
         * xppl: 爷爷节点的左节点(这是爷爷的小儿子:左hash > 父hash)
         * xppl: 爷爷节点的右节点(这是爷爷的大儿子:右hash < 父hash)
         */
        for (TreeNode<K, V> xp, xpp, xppl, xppr;;) {
            /**
             * 如果当前节点的父节点为空,则说明当前节点是根节点,根据红黑树特性2,将当前节点标记为黑色;
             *
             * 如果当前节点是根节点,因为是插入,所以说明当前直插入了一个节点,直接结束方法
             */
            if ((xp = x.parent) == null) {
                x.red = false;
                return x;

                // 如果父节点是黑色的,或者爷爷节点是空,则说明不需要做自平衡,直接结束方法
            } else if (!xp.red || (xpp = xp.parent) == null)
                return root;

            // 左子树操作,当前节点的父节点位于左子树
            if (xp == (xppl = xpp.left)) {

                // 如果叔叔节点不为空,且是红色
                if ((xppr = xpp.right) != null && xppr.red) {
                    // 将叔叔节点,父节点都变为黑色;爷爷节点变为红色
                    xppr.red = false;
                    xp.red = false;
                    xpp.red = true;
                    // 将当前节点指向爷爷节点,再走一次循环(验证爷爷节点是否满足特性)
                    x = xpp;

                    // 叔叔节点为空或者是黑色的
                } else {
                    // 当前节点是位于父节点右边的节点
                    if (x == xp.right) {
                        // 父节点进行左旋操作
                        root = rotateLeft(root, x = xp);
                        // 判断父节点是否为空,为空,则爷爷节点为空;不为空,则爷爷节点是父节点的父节点
                        xpp = (xp = x.parent) == null ? null : xp.parent;
                    }
                    // 父节点不为空
                    if (xp != null) {
                        // 将父节点变为黑色
                        xp.red = false;
                        // 如果爷爷节点不为空,将爷爷节点变为红色,并将爷爷节点右旋
                        if (xpp != null) {
                            xpp.red = true;
                            root = rotateRight(root, xpp);
                        }
                    }
                }
                // 右子树操作,当前节点的父节点位于右子树(与左子树类似)
            } else {
                if (xppl != null && xppl.red) {
                    xppl.red = false;
                    xp.red = false;
                    xpp.red = true;
                    x = xpp;
                } else {
                    if (x == xp.left) {
                        root = rotateRight(root, x = xp);
                        xpp = (xp = x.parent) == null ? null : xp.parent;
                    }
                    if (xp != null) {
                        xp.red = false;
                        if (xpp != null) {
                            xpp.red = true;
                            root = rotateLeft(root, xpp);
                        }
                    }
                }
            }
        }
    }

左旋操作:
image.png

右旋操作:
image.png

红黑树平衡时涉及到的线程读写锁

TreeBin的锁操作,没有基于AQS,仅仅是对一个变量的CAS操作和一些业务判断实现的。

每次读线程操作,对lockState+4。

写线程操作,对lockState 设置为 1,如果读操作占用着线程,就先设置 lockState为 2,表示当前有写线程在等待写锁

// TreeBin的锁操作
// 如果说有读线程在读取红黑树的数据,这时,写线程要阻塞(做平衡前)
// 如果有写线程正在操作红黑树(做平衡),读线程不会阻塞,会读取双向链表
// 读读不会阻塞!
static final class TreeBin<K,V> extends Node<K,V> {
  
    // waiter:等待获取写锁的线程
    volatile Thread waiter;
    // lockState:当前TreeBin的锁状态
    volatile int lockState;
  
    // 对锁状态进行运算的值
    // 有线程拿着写锁
    static final int WRITER = 1; 
    // 有写线程,再等待获取写锁
    static final int WAITER = 2; 
    // 读线程,在红黑树中检索时,需要先对lockState + READER
    // 这个只会在读操作中遇到
    static final int READER = 4; 

    // 加锁-写锁
    private final void lockRoot() {
        // 将lockState从0设置为1,代表拿到写锁成功
        if (!U.compareAndSwapInt(this, LOCKSTATE, 0, WRITER))
            // 如果写锁没拿到,执行contendedLock
            contendedLock(); 
    }

    // 释放写锁
    private final void unlockRoot() {
        lockState = 0;
    }

  
    // 写线程没有拿到写锁,执行当前方法
    private final void contendedLock() {
        // 是否有线程正在等待
        boolean waiting = false;
        // 死循环,s是lockState的临时变量
        for (int s;;) {
            // 
            // lockState & 11111101 ,只要结果为0,说明当前写锁,和读锁都没线程获取
            if (((s = lockState) & ~WAITER) == 0) {
                // CAS一波,尝试将lockState再次修改为1,
                if (U.compareAndSwapInt(this, LOCKSTATE, s, WRITER)) {
                    // 成功拿到锁资源,并判断是否在waiting
                    if (waiting)
                        // 如果当前线程挂起过,直接将之前等待的线程资源设置为null
                        waiter = null;
                    return;
                }
            }
            // 有读操作在占用资源
            // lockState &  00000010,代表当前没有写操作挂起等待。
            else if ((s & WAITER) == 0) {
                // 基于CAS,将LOCKSTATE的第二位设置为1
                if (U.compareAndSwapInt(this, LOCKSTATE, s, s | WAITER)) {
                    // 如果成功,代表当前线程可以waiting等待了
                    waiting = true;
                    waiter = Thread.currentThread();
                }
            }
            else if (waiting)
                // 挂起当前线程!会由写操作唤醒
                LockSupport.park(this);
        }
    }
}   
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值