Java编译器可以重新排序synchronized语句以进行优化吗?

时间:2013-10-07 01:39:14

标签: java multithreading

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

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

成为:

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

3 个答案:

答案 0 :(得分:4)

  

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

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

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

引用JSR 133 (Java Memory Model) FAQ

  

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

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

enter image description here

答案 1 :(得分:0)

是和否。

订单必须一致。

假设您在两个银行帐户之间创建了一个交易,并且始终首先获取发件人的锁定,然后抓住接收者的锁定。问题是 - 丹和鲍勃都希望同时向对方汇款。

线程1可能会抓住Dan的锁定,因为它会将Dan的交易处理给Bob 然后线程2抓住Bob的锁定,因为它将Bob的交易处理为Dan。

然后,bam,僵局。

道德是:

  1. 少锁。
  2. 阅读Java: Concurrency in Practice。我的例子是从那里拿走的。我喜欢和下一个人一样在编程中争论书籍的优点,但很少有人能够全面了解两个封面之间的难题,所以尽情享受吧。
  3. 所以这就是答案的一部分,我想你可能会试图提出的其他事情,因为期望是坚定的我,我是精神上的。

    JVM不会按照您编程的顺序获取锁定。我怎么知道这个?因为否则我在答案的前半部分就无法解决问题。

答案 2 :(得分: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点处理)。 HOWEVER 必不可少在整个程序中保持订单一致,以避免死锁。