1

同步语句是否可以重新排序。即:可以:

synchronized(A) {
   synchronized(B) {
     ......
   }
}

变得 :

synchronized(B) { 
    synchronized(A) { 
     ...... 
     }  
}
4

3 回答 3

5

同步语句可以重新排序吗?

我假设您在询问编译器是否可以重新排序synchronized块,以便锁定顺序以与代码不同的顺序发生。

答案是不。synchronized块(和字段访问)对编译器施加volatile了排序限制。在您的情况下,您不能在另一个监视器输入之前移动一个监视器输入,也不能在另一个监视器退出之后移动监视器退出。请参阅下面的网格。

引用JSR 133(Java 内存模型)常见问题解答

例如,编译器不可能在获取之前或发布之后移动您的代码。当我们说获取和释放作用于缓存时,我们是在使用速记来表示许多可能的影响。

Doug Lea 的JSR-133 Cookbook有一个显示重新排序可能性的网格。网格中的空白条目表示允许重新排序。在您的情况下,输入synchronized块是“MonitorEnter”(与加载字段相同的重新排序限制volatile),退出synchronized块是“MonitorExit”(与存储到volatile字段相同)。

在此处输入图像描述

于 2013-10-07T13:17:49.763 回答
0

是和不是。

顺序必须一致。

假设您在两个银行账户之间创建交易,并且总是先获取发送者的锁,然后再获取接收者的锁。问题是 - 假设 Dan 和 Bob 都想同时互相转账。

线程 1 可能会获取 Dan 的锁,因为它处理 Dan 与 Bob 的交易。
然后线程 2 获取 Bob 的锁,因为它处理 Bob 与 Dan 的交易。

然后,砰,僵局。

道德是:

  1. 少锁。
  2. 阅读Java:实践中的并发。我的例子取自那里。我喜欢争论编程书籍的优点和下一个人一样多,但是你很少能在两个封面之间全面覆盖一个困难的话题,所以好好享受吧。

所以这是答案的一部分,我猜你可能一直试图问其他事情,因为我坚信我会表现出通灵的期望。

JVM 不会以与您编程的顺序不同的顺序获取锁。我怎么知道这个?因为否则将无法解决我回答的前半部分的问题。

于 2013-10-07T01:44:48.920 回答
0

编译器永远不会重新排序同步语句,因为它对最终发生的事情有很大影响。

同步块用于获得对放置在同步括号之间的特定对象的锁定。

private final Object LOCK_1 = new Object();    

public void foo(){
    synchronized(LOCK_1){
        //code here...
    }
}

获取对象 LOCK_1 的锁,并在同步块完成时释放它。由于同步块用于防止并发访问,因此有时可能需要使用多个锁,尤其是在写入/读取多个线程不安全对象时。

考虑以下使用嵌套同步块的代码:

private final Object LOCK_1 = new Object();    
private final Object LOCK_2 = new Object();

public void bar(){
    synchronized(LOCK_1){
        //Point A
        synchronized(LOCK_2){
            //Point B
        }

        //Point C
    }
    //Point D
}

如果我们查看 A、B、C、D 点,我们可以理解为什么同步的顺序很重要。

首先在 A 点,获得 LOCK_1 的锁,因此任何其他试图获得 LOCK_1 的线程都被放入队列中。
在 B 点,当前执行的线程拥有 LOCK_1 和 LOCK_2 的
在 C 点,当前正在执行的线程已经释放了 LOCK_2 的锁
在 D 点,当前正在执行的线程已经释放了所有的锁。

如果我们翻转这个例子并决定将 LOCK_2 放在外部块上,您将意识到线程获取锁的顺序发生了变化,这对其最终执行的操作有很大影响。通常,当我使用同步块编写程序时,我对每个正在访问的线程不安全资源使用一个 MUTEX 对象(或每组一个 MUTEX)。假设我想使用 LOCK_1 从流中读取并使用 LOCK_2 写入流。认为交换锁定顺序意味着同样的事情是不合逻辑的。

考虑 LOCK_2(写锁)被另一个线程持有。如果我们在外部块上有 LOCK_1,则当前执行的线程至少可以在放入写入锁队列之前处理所有读取代码(本质上是在 A 点执行代码的能力)。如果我们颠倒锁的顺序,当前正在执行的线程最终将不得不等待写入完成,然后在保持写入锁的同时继续进行读写(一直到读取)

切换锁的顺序时出现的另一个问题(并非始终如一,一些代码首先具有LOCK_1,而另一些代码首先具有LOCK_2)。考虑两个线程都急切地尝试执行具有不同锁定顺序的代码。线程 1 在外部块中获取 LOCK_1,线程 2 从外部块中获取 LOCK_2。现在当线程 1 尝试获取 LOCK_2 时,它不能因为线程 2 拥有它。而当线程 2 尝试获取 LOCK_1 时,它也不能,因为线程 1 拥有它。两个线程本质上永远相互阻塞,形成死锁情况。

要回答您的问题,如果您想立即锁定两个对象而不在锁定之间进行任何类型的处理,那么顺序是无关紧要的(基本上在 A 点或 C 点没有处理)。但是必须在整个程序中保持顺序一致以避免死锁。

于 2013-10-07T02:05:27.470 回答