8

java meomry 模型要求synchronize在同一监视器上同步的块对在这些块中修改的变量强制执行前后处理。例子:

// in thread A
synchronized( lock )
{
  x = true;
}

// in thread B
synchronized( lock )
{
  System.out.println( x );
}

x==true在这种情况下,只要线程 A 已经通过了该块,线程 B 就会看到synchronized。现在我正在重写大量代码以使用更灵活(据说更快)的锁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();

但是,我没有在内存模型规范中看到任何暗示这种锁也暗示着必要的排序。研究实现它似乎依赖于对内部 volatile 变量的访问AbstractQueuedSynchronizer(至少对于 sun 实现)。然而,这不是任何规范的一部分,而且对非易失性变量的访问并没有真正考虑由这些变量给出的内存屏障覆盖,是吗?

所以,这是我的问题:

  • synchronized假设与“旧”块相同的顺序是否安全?
  • 这是在某处记录的吗?
  • 访问任何 volatile 变量是否是任何其他变量的内存屏障?

问候, 史蒂芬

--

对亚纳蒙的评论:

看下面的代码:

// 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 变量相比呢?

4

4 回答 4

5

API 文档

所有 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.

不成功的锁定和解锁操作,以及重入锁定/解锁操作,不需要任何内存同步效果。

于 2010-04-05T00:43:53.337 回答
4

除了内存模型的语义保证什么问题之外,我认为您发布的代码存在一些问题。

  1. 您在同一个锁上同步两次- 这是不必要的。使用Lock实现时,您不必使用该synchronized块。
  2. 使用 a 的标准习惯用法Lock是在 try-finally 块中这样做,以防止意外解锁锁(因为在进入您所在的任何块时,锁不会自动释放,与synchronized块一样)。

您应该使用 aLock类似的东西:

lock.lock();
try {
    //do stuff
}
finally { 
    lock.unlock();
}
于 2010-04-05T01:10:09.483 回答
1

读取和写入 volatile 变量现在强制发生在操作排序之前和之后。写入 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因为它被设计为使用。

  1. 使用Lock带有 Java 内置syncronized关键字的 a 可以有效地使访问锁已经是单线程的,因此它没有Lock机会做任何实际工作。
  2. 获取发布 aLock应该按照下面的模式完成,这在 java 文档中概述Lock

lock.readLock().lock();
try {
  // Do work
} finally {
  lock.readLock.unlock();
}

于 2010-04-05T01:11:53.037 回答
1

Yanamon,我不确定您是否正确-但原因与您提出的论点不同。

unguardedVariable 变量可以在线程“a”中重新排序,以便在将 memoryBarrier 设置为 true后将其值设置为 10。

“不能保证一个线程中的操作将按照程序给定的顺序执行,只要在线程中无法检测到重新排序 -即使重新排序对其他线程来说是显而易见的

Java 并发实践,Brian Goetz,p34

更新:在旧内存模型的情况下,我所说的是正确的。所以,如果你想在任何地方写一次运行,那么我的论点就成立了。然而,在新的内存模型中,情况并非如此,因为在存在易失性访问的情况下,围绕非易失性变量重新排序的语义变得更加严格(参见http://www.cs.umd.edu/~pugh /java/memoryModel/jsr-133-faq.html#volatile)。

于 2010-10-30T13:02:02.297 回答