我在第14.6.1节中读到了在"Java in Practice“中实现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;
}我相信这是nonfairTryAcquire在ReentrantLock.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()之后( state是AQS的volatile限定变量),但是在owner设置之后,根本就没有任何东西(可以强制执行同步语义)。会发生数据竞争吗?
好吧,鉴于这本书的权威和经过彻底测试的代码,我想到了两种可能性:
owner = current之前的全部障碍(不管是mfence还是‘lock’‘ed指令)做了隐藏的工作。但我从几篇著名的文章中了解到,完全障碍更关心前面的书写以及后面的阅读。好吧,如果这种可能性成立,那么"JCIP“中的一些句子可能会被错误地表述。setState(c+1)实际上是在owner = current之后,尽管它位于if-else的另一个分支中。如果评论说的是事实,这是否意味着setSate(c+1)插入的屏障可以将同步语义强加给另一个分支中的owner = current?我是这个领域的新手,几个伟大的博客帮助我理解JVM的基础(没有排序):
以及永远辉煌的:http://g.oswego.edu/dl/jmm/cookbook.html
在做完家庭作业和上网之后,我没有得出令人满意的结论。
如果这句话太冗长或含糊不清,请原谅(英语不是我的母语)。请帮我做这件事,任何相关的东西我们都很感激。
发布于 2013-09-12 07:25:21
您怀疑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。
因此,
c=1到达c=1,则null,这也很好。
c>1到达c>1,则
我感到愤慨的是,JCIP中的脚注“只在调用getState之后才读取所有者字段,而只在调用setState之前才写它”是有误导性的。它在调用owner之前编写tryRelease,而不是tryAcquire。
发布于 2013-09-11 05:07:53
这一点在这篇博客文章中解释得很好。底线是,当读取线程读取易失性字段时,在写入易失性字段之前修改了的所有写入线程更新的字段对读取线程也是可见的。锁类组织字段访问,以确保只有状态字段需要易失性,并且在需要时所有者字段仍然是安全的。
https://stackoverflow.com/questions/18732088
复制相似问题