如何在ReentrantLock.Sync中搭载当前线程变量?

时间:2013-09-11 03:17:26

标签: java multithreading concurrency synchronization memory-barriers

我在“实践中的Java并发”第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;
     }
}

我相信这是nonfirTryAcquireReentrantLock.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)可见。实际上,owner的读取是在调用getState()之后(而statevolatile的{​​{1}}合格变量),但在设置之后AQS,根本没有(可以施加同步语义)。数据竞争发生了吗?

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

  1. 在设置owner之前完整屏障(无论是mfence还是'lock'ed指令)执行隐藏的工作。但是从我从几篇着名的文章中学到的,完整的屏障更关心它之前的写作以及它之后的读取。好吧,如果这种可能性成立,那么“JCIP”中的一些句子可能会被不适当地陈述。

  2. 我注意到'地理位置'owner = current确实在代码段中setState(c+1)之后,尽管它位于if-else的另一个分支中。如果评论说的是事实,是否意味着owner = current插入的障碍可以在另一个分支的setSate(c+1)上强加同步语义?

  3. 我是这方面的新手,几个很棒的博客帮助我理解了JVM的基础(没有订购):

    以及永远华丽的:http://g.oswego.edu/dl/jmm/cookbook.html

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

    请原谅我,如果这太冗长或不清楚(英语不是我的母语)。请帮助我,赞赏任何相关内容。

2 个答案:

答案 0 :(得分: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:         }

此处所有者在状态设置为0之前设置为null。要初始获取锁定,状态必须为0,因此所有者为null

因此,

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

我认为脚注“仅在调用getState之后读取所有者字段,而在JCIP中调用setState 之前才写入它”是误导性的。它在owner中调用setState之前写tryRelease,而不是tryAcquire

答案 1 :(得分:1)

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