我正在阅读JDK8中ConcurrentHashMap的源代码,请注意TreeBin使用“读写”锁来防止并发读写。

如果没有并发的写线程试图修改树结构,则读线程将通过TreeNodes。当“查找”操作完成时,读取线程可能会:

(1)'CAS'的lockState和'unpark'的waiter(writer)线程(如果存在)。

以下是源代码中的“find()”方法。

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;
                            // (1)if no more readers, try to unpark the waiter if it exists
                            if (U.getAndAddInt(this, LOCKSTATE, -READER) ==
                                (READER|WAITER) && (w = waiter) != null)
                                LockSupport.unpark(w);
                        }
                        return p;
                    }
                }
            }
            return null;
        }

另一方面,编写者线程可能会:
  • (2)通过“CAS”操作将WAITER状态添加到lockState中。
  • (3)将自身设置为waiter变量。
  • (4)“停放”本身。

  • 这是作者的代码:

            private final void contendedLock() {
                boolean waiting = false;
                for (int s;;) {
                    if (((s = lockState) & ~WAITER) == 0) {
                        if (U.compareAndSwapInt(this, LOCKSTATE, s, WRITER)) {
                            if (waiting)
                                waiter = null;
                            return;
                        }
                    }
                    else if ((s & WAITER) == 0) {
                        if (U.compareAndSwapInt(this, LOCKSTATE, s, s | WAITER)) {
                            waiting = true;
                            waiter = Thread.currentThread();
                        }
                    }
                    else if (waiting)
                        LockSupport.park(this);
                }
            }
    

    这是我的困惑:

    如果以上四个操作按此顺序(2)(1)(3)(4)运行,则操作(1)不会取消任何操作,因为此时“waiter”为空。

    然后,服务员将永远停放,而没有任何人可以取消停放。

    随后的写入将全部锁定在由“驻留”线程持有的固有锁定上。

    这是僵局的机会吗?

    我对此很困惑。我想也许我已经错过了源代码中的某些内容。如果您熟悉它,需要您的帮助。

    最佳答案

    这个问题已有一年多了。但这是一个很好的难题。答案是:

    在(2)(1)(3)之后,在contendedLock()中继续执行如下:
    if (((s = lockState) & ~WAITER) == 0)是真的,因为执行了(1)
    if (U.compareAndSwapInt(this, LOCKSTATE, s, WRITER))也是 true ,因为(3)在(s = lockState)之前而不是之后执行

    由于在执行(3)之前waiting设置为true,因此第三个if语句也是 true 。因此,waiter设置为null,我们退出循环。 (4)永远不会执行。

    把它们加起来:
    (2)(1)(3)之后,将不会执行操作(4)。因此,没有死锁的机会,我们所有人都可以继续使用ConcurrentHashMap而无需担心;-)

    09-25 17:08