Java 内存模型:重新排序和并发锁
java meomry 模型要求在同一监视器上同步的synchronize
块对这些块中修改的变量强制执行前后关系。示例:
// in thread A
synchronized( lock )
{
x = true;
}
// in thread B
synchronized( lock )
{
System.out.println( x );
}
在这种情况下,只要线程 A 已经通过了该 synchronized
块,就保证线程 B 将看到 x==true
。现在,我正在重写大量代码,以使用 java.util.concurrent
中更灵活(据说更快)的锁,尤其是 ReentrantReadWriteLock
。所以这个例子看起来像这样:
编辑:这个例子被破坏了,因为我错误地转换了代码,正如matt b所指出的。修正如下:
// in thread A
lock.writeLock().lock();
{
x = true;
}
lock.writeLock().unlock();
// in thread B
lock.readLock().lock();
{
System.out.println( x );
}
lock.readLock().unlock();
但是,我没有在内存模型规范中看到任何提示,表明此类锁也意味着必要的排序。查看实现,它似乎依赖于对 AbstractQueuedSynchronizer 内部易失性变量的访问(至少对于 sun 实现而言)。然而,这不是任何规范的一部分,而且对非易失性变量的访问并不真正被视为由这些变量给出的内存屏障覆盖,不是吗?
所以,我的问题是:
- 假设与“旧”同步块相同的顺序是否安全?
- 这有记录在某处吗?
- 访问任何易失性变量是否会成为任何其他变量的内存屏障?
问候, Steffen
-
对 Yanamon 的评论:
看下面的代码:
// in thread a
x = 1;
synchronized ( a ) { y = 2; }
z = 3;
// in thread b
System.out.println( x );
synchronized ( a ) { System.out.println( y ); }
System.out.println( z );
根据我的理解,内存屏障强制第二个输出显示 2,但对其他变量没有保证的影响......?那么这与访问 volatile 变量有何不同呢?
The java meomry model mandates that synchronize
blocks that synchronize on the same monitor enforce a before-after-realtion on the variables modified within those blocks. Example:
// in thread A
synchronized( lock )
{
x = true;
}
// in thread B
synchronized( lock )
{
System.out.println( x );
}
In this case it is garanteed that thread B will see x==true
as long as thread A already passed that synchronized
-block. Now I am in the process to rewrite lots of code to use the more flexible (and said to be faster) locks in java.util.concurrent
, especially the ReentrantReadWriteLock
. So the example looks like this:
EDIT: The example was broken, because I incorrectly transformed the code, as noted by matt b. Fixed as follows:
// in thread A
lock.writeLock().lock();
{
x = true;
}
lock.writeLock().unlock();
// in thread B
lock.readLock().lock();
{
System.out.println( x );
}
lock.readLock().unlock();
However, I have not seen any hints within the memory model specification that such locks also imply the nessessary ordering. Looking into the implementation it seems to rely on the access to volatile variables inside AbstractQueuedSynchronizer
(for the sun implementation at least). However this is not part of any specification and moreover access to non-volatile variables is not really condsidered covered by the memory barrier given by these variables, is it?
So, here are my questions:
- Is it safe to assume the same ordering as with the "old"
synchronized
blocks? - Is this documented somewhere?
- Is accessing any volatile variable a memory barrier for any other variable?
Regards,
Steffen
--
Comment to Yanamon:
Look at the following code:
// in thread a
x = 1;
synchronized ( a ) { y = 2; }
z = 3;
// in thread b
System.out.println( x );
synchronized ( a ) { System.out.println( y ); }
System.out.println( z );
From what I understood, the memory barrier enforces the second output to show 2, but has no guaranteed affect on the other variables...? So how can this be compared to accessing a volatile variable?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
来自 API 文档 :
From the API-doc:
除了内存模型的语义保证什么的问题之外,我认为您发布的代码还存在一些问题。
Lock
实现时,您不需要使用synchronized
块。Lock
的标准习惯用法是在 try-finally 块中执行此操作,以防止意外解锁锁(因为当进入您所在的任何块时,锁不会自动释放,就像同步
块)。您应该使用类似于以下内容的
Lock
:Beyond the question of what the semantics of the memory model guarantees, I think there are a few problems with the code you are posting.
Lock
implementation, you don't have need to use thesynchronized
block.Lock
is to do so in a try-finally block to prevent accidental unlocking of the lock (since the lock is not automatically released when entering whatever block you are in, as with thesynchronized
block).You should be using a
Lock
with something resembling:现在,读取和写入易失性变量强制发生在操作排序之前和之后。写入易失性变量与释放监视器具有相同的效果,而读取变量与获取监视器具有相同的效果。下面的示例使它更清楚一点:
但是话虽如此,您提供的示例代码看起来并不像真正使用
ReentrantLock
因为它的设计用途是这样的。Lock
与 Java 内置的syncronized
关键字一起使用,可以有效地使对已经是单线程的锁的访问变得有效,因此它不会给出Lock 有机会做任何真正的工作。
Lock
应遵循以下模式,这在锁定
Reading and writing volatile variables now enforces happens before and happens after operation ordering. Writing to a volatile variable has the same effect as releasing a monitor and reading a variable has the effect as acquiring a monitor. The following example makes it a little more clear:
But that all being said the sample code you provided did not look like it was really using the
ReentrantLock
as it was designed to be used.Lock
with the the Java's built insyncronized
keyword effectively makes access to the lock already single threaded so it doesn't give theLock
a chance to do any real work.Lock
should be done following the pattern below, this is outlined in the java docs ofLock
Yanamon,我不确定你是否正确 - 但出于与你提出的论点不同的原因。
unguardedVariable 变量可以在线程“a”中重新排序,以便在 memoryBarrier 设置为 true 后将其值设置为 10。
“只要该线程内无法检测到重新排序,就不能保证一个线程中的操作将按照程序给定的顺序执行 - 即使重新排序对于其他线程”
Java 并发实践,Brian Goetz,p34
更新:我所说的在旧内存模型的情况下是正确的。因此,如果您想要一次编写,随处运行,那么我的论点是成立的。然而,在新的内存模型中,情况并非如此,因为在存在易失性访问的情况下围绕非易失性变量重新排序的语义变得更加严格(请参阅 http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq .html#易失性)。
Yanamon, I am not sure you are correct - but for different reasons than the argument you are making.
The unguardedVariable variable may be re-ordered in thread "a" such that its value is set to 10 after memoryBarrier is set to true.
"There is no guarantee that operations in one thread will be performed in the order given by the program, as long as the reordering is not detectable within that thread - even if the reordering is apparent to other threads"
Java Concurrency in Practise, Brian Goetz, p34
UPDATED: what I said is true in the case of the old memory model. So, if you want write-once-run-anywhere then my argument stands. However, in the new memory model, it is not the case as the semantics surrounding re-ordering of non-volatile variables in the presence of volatile access has become stricter (see http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#volatile).