Java内存模型:重新排序和并发锁

时间:2010-04-05 00:36:44

标签: java concurrency locking synchronized

java meomry模型要求在同一监视器上同步的synchronize块强制执行在这些块中修改的变量之前的实现。例如:

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

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

在这种情况下,只要线程A已经通过x==true - 块,线程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();

但是,我没有在内存模型规范中看到任何提示,这些锁也意味着必须进行排序。查看实现,它似乎依赖于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变量进行比较?

4 个答案:

答案 0 :(得分:5)

来自API-doc

  

所有Lock实施都必须强制执行   相同的内存同步   由内置提供的语义   监视器锁,如The。中所述   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.
     

锁定和解锁失败   操作和重入   锁定/解锁操作,不要   需要任何内存同步   的效果。

答案 1 :(得分:4)

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

  1. 您正在同一个锁上同步两次 - 这是不必要的。使用Lock实施时,您无需使用synchronized块。
  2. 使用Lock的标准习惯是在try-finally块中执行此操作以防止意外解锁(因为在进入任何块时锁不会自动释放,如同synchronized阻止)。
  3. 你应该使用类似的Lock

    lock.lock();
    try {
        //do stuff
    }
    finally { 
        lock.unlock();
    }
    

答案 2 :(得分:1)

现在,读取和写入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关键字有效地使得锁已经单线程访问,因此它不会给Lock一个机会任何真正的工作。
  2. 应按照以下模式获取Lock的发布,这在Lock
  3. 的java文档中有所概述
    lock.readLock().lock();
    try {
      // Do work
    } finally {
      lock.readLock.unlock();
    }
    

答案 3 :(得分:1)

Yanamon,我不确定你是否正确 - 但出于与你正在论证的论点不同的原因。

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

“无法保证一个线程中的操作将按程序给出的顺序执行,只要在 其他线程显而易见

Java Concurrency in Practice,Brian Goetz,p34

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