Java内存模型:重新排序和并发锁定
java meomry模型要求同步在同一监视器上synchronize
块对这些块中修改的变量强制执行之前的实现。 例:
// in thread A synchronized( lock ) { x = true; } // in thread B synchronized( lock ) { System.out.println( x ); }
在这种情况下,只要线程A已经通过了synchronized
-block,就可以保证线程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
内部的volatile变量的访问(至少对于sun实现)。 然而,这不是任何规范的一部分,而且这些变量给出的内存障碍并没有真正考虑到对非易失性变量的访问,是吗?
所以,这是我的问题:
- 采用与“旧”
synchronized
块相同的顺序是否安全? - 这是在某处记录的吗?
- 访问任何volatile变量是否为任何其他变量的内存屏障?
此致,斯蒂芬
–
对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变量进行比较呢?
来自API-doc :
所有Lock实现必须强制执行内置监视器锁提供的相同内存同步语义,如Java语言规范,第三版(17.4内存模型)中所述:
* A successful lock operation has the same memory synchronization effects as a successful Lock action. * A successful unlock operation has the same memory synchronization effects as a successful Unlock action.
不成功的锁定和解锁操作以及重入锁定/解锁操作不需要任何内存同步效果。
除了内存模型的语义保证的问题之外,我认为您发布的代码存在一些问题。
- 您在同一个锁上同步两次 – 这是不必要的。 使用
Lock
实现时,您无需使用synchronized
块。 - 使用
Lock
的标准习惯是在try-finally块中执行此操作以防止意外解锁(因为在进入任何块时锁定不会自动释放,与synchronized
块一样)。
你应该使用类似的东西:
lock.lock(); try { //do stuff } finally { lock.unlock(); }
现在,读取和写入volatile变量会强制执行,并在操作排序之后执行。 写入易失性变量与释放监视器具有相同的效果,读取变量具有获取监视器的效果。 以下示例使其更加清晰:
volatile boolean memoryBarrier = false; int unguardedValue = 0; //thread a: unguardedValue = 10; memoryBarrier = true; // thread b if (memoryBarrier) { // unguardedValue is guaranteed to be read as 10; }
但是,所有人都说你提供的示例代码看起来并没有真正使用ReentrantLock
因为它被设计为使用。
- 围绕使用
Lock
与Java的内置syncronized
关键字有效地使得锁已经单线程访问,因此它不会给Lock
做任何实际工作的机会。 - 获取释放
Lock
应该按照下面的模式进行,这在Lock
的java文档中有概述
lock.readLock().lock(); try { // Do work } finally { lock.readLock.unlock(); }
Yanamon,我不确定你是否正确 – 但出于不同的原因而不是你正在提出的论点。
可以在线程“a”中重新排序unguardedVariable变量,使得在 memoryBarrier设置为true 之后将其值设置为10。
“无法保证一个线程中的操作将按程序给出的顺序执行,只要在该线程内无法检测到重新排序 – 即使重新排序对其他线程很明显 ”
Java Concurrency in Practice,Brian Goetz,第34页
更新:在旧内存模型的情况下,我所说的是正确的。 所以,如果你想在任何地方写一次运行,那么我的论点就是这样。 然而,在新的内存模型中,并非如此,因为在存在易失性访问的情况下重新排序非易失性变量的语义变得更加严格(参见http://www.cs.umd.edu/~pugh) /java/memoryModel/jsr-133-faq.html#volatile )。