9

我在“Java Concurrency in Practice”第 14.6.1 节中阅读了 ReentrantLock 实现的一些细节,注释中的某些内容让我感到困惑:

因为受保护的状态操作方法具有易失性读取或写入的内存语义,并且 ReentrantLock仅在调用 getState 后仔细读取所有者字段仅在调用 setState 之前写入,所以 ReentrantLock 可以捎带同步状态的内存语义,从而避免进一步的同步参见第 16.1.4 节。

它所指的代码:

protected boolean tryAcquire(int ignored) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c ==0) {
        if (compareAndSetState(0, 1)) {
             owner = current;
             return true;
        }
     } else if (current == owner) {
         setState(c+1);
         return true;
     }
     return false;
}

我相信这nonfairTryAcquireReentrantLock.Sync.

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

因此,令人困惑的部分是 的设置(owner它只是 中的普通实例变量)如何在其他线程中AbstractOwnableSynchronizer变得可见。确实,在调用 之后else if (current == owner)读取(并且是 的限定变量),但是在设置 之后,根本没有任何东西(可以施加同步语义)。数据竞赛发生了吗?ownergetState()statevolatileAQSowner

好吧,鉴于本书的权威性和经过彻底测试的代码,我想到了两种可能性:

  1. owner = current设置执行隐藏工作之前的完整障碍(无论是 mfence 还是“锁定”指令) 。但是从我从几篇著名的文章中了解到,full barrier 更关心它之前的写入以及之后的读取。好吧,如果这种可能性成立,那么“JCIP”中的某些句子可能会被不当陈述。

  2. 我注意到代码片段中的“地理上”setState(c+1)确实出现在后面,尽管它位于 if-else 的另一个分支中。owner = current如果评论说的是真的,这是否意味着插入的屏障可以在另一个分支setSate(c+1)上施加同步语义?owner = current

我是这个领域的新手,一些很棒的博客帮助我理解了 JVM 的底层(无排序):

以及总是宏伟的:http: //g.oswego.edu/dl/jmm/cookbook.html

在做完功课和搜索互联网后,我未能得出令人满意的结论。

如果这太罗嗦或不清楚,请原谅我(英语不是我的母语)。请帮我解决这个问题,任何相关的事情都会受到赞赏。

4

2 回答 2

3

owner = current;您怀疑(在 CAS 之后)和if (current == owner)(在读取状态并检查它是否 >0 之后)之间可能存在竞争。

单独来看这段代码,我认为你的推理是正确的。但是,您还需要考虑tryRelease

 123:         protected final boolean tryRelease(int releases) {
 124:             int c = getState() - releases;
 125:             if (Thread.currentThread() != getExclusiveOwnerThread())
 126:                 throw new IllegalMonitorStateException();
 127:             boolean free = false;
 128:             if (c == 0) {
 129:                 free = true;
 130:                 setExclusiveOwnerThread(null);
 131:             }
 132:             setState(c);
 133:             return free;
 134:         }

这里的所有者是null在状态设置为 0 之前设置的。要最初获取锁,状态必须为 0,因此所有者为null

最后,

  • 如果一个线程到达if (current == owner)with c=1
    • 它可以是拥有线程,在这种情况下,所有者是正确的并且状态会增加。
    • 它可以是另一个线程,可以看到或看不到新所有者。
      • 如果它看到它,一切都很好。
      • 如果没有,它会看到null,这也很好。
  • 如果一个线程到达if (current == owner)with c>1
    • 它可以是拥有线程,在这种情况下,所有者是正确的并且状态会增加。
    • 它可以是另一个线程,但所有者肯定是正确的。

我同意 JCIP 中的脚注“仅在调用 getState 之后读取所有者字段并仅在调用 setState 之前写入它”具有误导性。它写了ownerbefore call setStatein tryRelease,但不是tryAcquire

于 2013-09-12T07:25:21.013 回答
1

这在这篇博文中得到了很好的解释。底线是,当读取线程读取 volatile 字段时,在写入 volatile 字段之前由写入线程更新的所有字段也将对读取线程可见。锁类组织字段访问以确保只有状态字段需要是可变的,并且所有者字段在需要时仍然安全地传播

于 2013-09-11T05:07:53.977 回答