Java ReentrantLock.Sync中当前线程变量的装载是如何工作的?
我在14.6.1节的“Java并发实践”中读到了一些关于ReentrantLock实现的细节,注释中的一些内容让我感到困惑: 由于受保护状态操作方法具有易失性读或写的内存语义,并且ReentrantLock仅在调用getState之后读取所有者字段,并且在调用setState之前写入所有者字段,因此ReentrantLock可以利用同步状态的内存语义,从而避免进一步同步,见第16.1.4节 它所指的代码:Java ReentrantLock.Sync中当前线程变量的装载是如何工作的?,java,multithreading,concurrency,synchronization,memory-barriers,Java,Multithreading,Concurrency,Synchronization,Memory Barriers,我在14.6.1节的“Java并发实践”中读到了一些关于ReentrantLock实现的细节,注释中的一些内容让我感到困惑: 由于受保护状态操作方法具有易失性读或写的内存语义,并且ReentrantLock仅在调用getState之后读取所有者字段,并且在调用setState之前写入所有者字段,因此ReentrantLock可以利用同步状态的内存语义,从而避免进一步同步,见第16.1.4节 它所指的代码: protected boolean tryAcquire(int ignored) {
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;
}
我相信这是ReentrantLock.Sync
中nonfairTryAcquire
的简化代码
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;
}
最终布尔非空获取(int获取){
最终螺纹电流=Thread.currentThread();
int c=getState();
如果(c==0){
if(比较设置状态(0,获取)){
设置ExclusiveOwnerThread(当前);
返回true;
}
}
else if(当前==getExclusiveOwnerThread()){
int nextc=c+收购;
if(nextc<0)//溢出
抛出新错误(“超过最大锁计数”);
设置状态(nextc);
返回true;
}
返回false;
}
因此,令人困惑的部分是,如果在其他线程中(current==owner),则所有者的设置(它只是AbstractOwnableSynchronizer
中的一个普通实例变量)如何对可见。实际上,owner
的读取是在调用getState()
之后进行的(并且state
是AQS
的volatile
限定变量),但是在设置owner
之后,根本没有任何东西(可以施加同步语义)。数据竞赛发生了吗
鉴于本书的权威性和经过彻底测试的代码,我想到了两种可能性:
设置owner=current
之前的完整屏障(无论是mfence还是“锁定”指令)执行隐藏操作。但从我从几篇著名的文章中了解到,完整的屏障更关注它之前的书写以及之后的阅读。好吧,如果这种可能性成立,那么“JCIP”中的一些句子可能会被不恰当地表述
我注意到,setState(c+1)
在代码片段中真正位于owner=current
之后,尽管它位于if-else的另一个分支中。如果评论所说的是事实,是否意味着setstate(c+1)
插入的障碍可以将同步语义强加给另一个分支中的owner=current
我是这方面的新手,有几个很棒的博客帮助我了解JVM的基础(无需排序):
以及永远华丽的:
在做完家庭作业和上网后,我没有得出令人满意的结论
请原谅,如果这太冗长或不清楚(英语不是我的母语)。请帮我做这件事,任何相关的事情都很感激。这在中解释得很好。底线是,当读取线程读取volatile字段时,写入线程在写入volatile字段之前修改的所有字段也将对读取线程可见。lock类组织字段访问,以确保只有state字段需要易变,并且在需要时所有者字段仍然安全地分配您怀疑owner=current之间可能存在竞争代码>(在CAS之后)和如果(当前==所有者)
(在读取状态并检查其是否>0之后)
单独来看这段代码,我认为您的推理是正确的。但是,你也需要考虑<代码> TryPrave<代码>:
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
,这也很好李>
- 如果线程通过
c>1到达If(current==owner)
,
- 它可以是拥有线程,在这种情况下,所有者是正确的,状态是递增的
- 它可以是另一个线程,但所有者肯定是正确的李>
我认为JCIP中的脚注“仅在调用getState后读取所有者字段,而仅在调用setState之前写入所有者字段”具有误导性。它在调用tryRelease
中的setState
之前写入owner
,但不写入tryAcquire
这正是让我困惑的地方:在CAS操作之后,所有者字段被更新为volatile字段。所谓的背驮在这里似乎不起作用。不过,感谢您的链接,另一个链接进入了集合。是的,但所有者字段的内容唯一重要的时候是在我们知道c!=0,因此我们也知道我们是拥有线程,因此内存模型中变量的状态不会有问题(如果我们不是拥有线程,那么owner==thread.currentThread()
我明白了。你的解释很有道理。可能是我太专注于栅栏的事情了,这只是缩小了我的视野,忽略了逻辑上的原因。但是说到我上面的问题,你能帮我得出一个结论吗