简体   繁体   English

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

[英]Can the synchronized statements be re-ordered by Java compiler for optimization?

Can the synchronization statements be reordered. 可以重新排序同步语句。 ie : Can : 即:可以:

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

become : 成为:

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

Can the synchronization statements be reordered? 可以重新排序同步语句吗?

I assume you are asking if the compiler can reorder the synchronized blocks so the lock order happens in a different order than the code. 我假设您在询问编译器是否可以对synchronized块重新排序,因此锁定顺序的顺序与代码不同。

The answer is no. 答案是不。 A synchronized block (and a volatile field access) impose ordering restrictions on the compiler. synchronized块( volatile字段访问)对编译器施加排序限制。 In your case, you cannot move a monitor-enter before another monitor-enter nor a monitor-exit after another monitor-exit. 在您的情况下,您无法在另一个监视器退出之前移动监视器 - 输入,也不能在另一个监视器退出之后移动监视器退出。 See the grid below. 请参见下面的网格。

To quote from JSR 133 (Java Memory Model) FAQ : 引用JSR 133(Java内存模型)FAQ

It is not possible, for example, for the compiler to move your code before an acquire or after a release. 例如,编译器无法在获取之前或发布之后移动代码。 When we say that acquires and releases act on caches, we are using shorthand for a number of possible effects. 当我们说获取和释放对缓存起作用时,我们使用速记来表示许多可能的影响。

Doug Lea's JSR-133 Cookbook has a grid which shows the reordering possibilities. Doug Lea的JSR-133 Cookbook有一个网格,显示重新排序的可能性。 A blank entry in the grid means that reordering is allowed. 网格中的空白条目表示允许重新排序。 In your case, entering a synchronized block is a "MonitorEnter" (same reordering limitations as loading of a volatile field) and exiting a synchronized block is a "MonitorExit" (same as storing to a volatile field). 在您的情况下,输入synchronized块是“MonitorEnter”(与加载volatile字段相同的重新排序限制)并退出synchronized块是“MonitorExit”(与存储到volatile字段相同)。

在此输入图像描述

Yes and no. 是的,不是。

The order must be consistent. 订单必须一致。

Suppose you are creating a transaction between two bank accounts, and always grab the sender's lock first, then grab the receiver's lock. 假设您在两个银行账户之间创建了一个交易,并且总是首先抓住发送方的锁,然后抓住接收方的锁。 Problem is - say both Dan and Bob want to transfer money to each other at the same time. 问题是 - 丹和鲍勃都希望同时向对方汇款。

Thread 1 might grab Dan's lock, as it processes Dan's transaction to Bob. 线程1可能会抓住Dan的锁定,因为它将Dan的交易处理给Bob。
Then thread 2 grab's Bob's lock, as it processes Bob's transaction to Dan. 然后线程2抓住Bob的锁,因为它将Bob的交易处理给Dan。

Then, bam, deadlock. 然后,bam,僵局。

The morals are: 道德是:

  1. Lock less. 少锁。
  2. Read Java: Concurrency in Practice . 阅读Java:实践中的并发 My example is taken from there. 我的例子是从那里拿走的。 I like arguing about the merits of books in programming as much as the next guy, but it's pretty rare you get comprehensive coverage of a difficult topic between two covers, so enjoy it. 我喜欢和下一个人一样讨论编程中书籍的优点,但很少有人能够全面了解两个封面之间的难题,所以尽情享受吧。

So this is the part of the answer where I guess at other things you might have been trying to ask instead, because the expectation is firmly on me that I act psychic. 所以这就是答案的一部分,我想你可能会试图提出的其他事情,因为期望是我坚定的,我是精神上的。

The JVM will not acquire the locks in an order different from which you have programmed. JVM不会以与您编程的顺序不同的顺序获取锁。 How do I know this? 我怎么知道这个? Because otherwise it would not be possible to solve the problem in the first half of my answer. 因为否则在我答案的前半部分就无法解决问题。

Synchronized statements are never reordered by the compiler as it has a big effect on what ends up happening. 编译器永远不会对同步语句进行重新排序,因为它会对最终发生的事情产生重大影响。

Synchronized blocks are used to obtain a lock on the specific Object placed between the synchronized parenthesis. 同步块用于获取位于同步括号之间的特定对象的锁定。

private final Object LOCK_1 = new Object();    

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

Obtains the lock for Object LOCK_1 and releases it when the synchronization block completes. 获取对象LOCK_1的锁定,并在同步块完成时释放它。 Since synchronization blocks are used to guard against concurrent access it may be sometimes required to use multiple locks especially when multiple thread-unsafe objects are being written/read to/from. 由于同步块用于防止并发访问,因此有时可能需要使用多个锁,尤其是在向/从写入/读取多个线程不安全对象时。

Consider the following code that uses a nested synchronization block: 请考虑以下使用嵌套同步块的代码:

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
}

If we look at points A,B,C,D we can realize why the order of synchronization matters. 如果我们看一下A,B,C,D点,我们就能明白为什么同步的顺序很重要。

First at point A, the lock for LOCK_1 is obtained therefore any other threads trying to obtain LOCK_1 is put into a queue. 首先在A点,获得LOCK_1的锁,因此任何其他尝试获取LOCK_1的线程都被放入队列中。
At point B, the currently executing thread owns the lock for both LOCK_1 and LOCK_2. 在B点,当前执行的线程拥有两个 LOCK_1和LOCK_2锁。
At point C, the currently executing thread has released the lock for LOCK_2 在C点,当前正在执行的线程已释放 LOCK_2的锁定
At point D, the currently executing thread has released all locks. 在D点,当前正在执行的线程已释放所有锁。

If we flip this example around and decided to put LOCK_2 on the outer block, you will realize that the thread's order of obtaining locks changes which has a big effect on what it ends up doing. 如果我们翻转这个例子并决定将LOCK_2放在外部块上,你会发现线程获取锁定的顺序发生变化,这对它最终做的事情有很大的影响。 Normally, when I make programs with synchronization blocks I use one MUTEX object per thread-unsafe resource I am accessing (or one MUTEX per group). 通常,当我使用同步块创建程序时,我每个线程不安全的资源使用一个MUTEX对象(或每个组一个MUTEX)。 Say I want to read from a stream using LOCK_1 and write to a stream using LOCK_2. 假设我想使用LOCK_1从流中读取并使用LOCK_2写入流。 It would be illogical to think that swapping the locking order around means the same thing. 认为交换锁定顺序意味着同样的事情是不合逻辑的。

Consider that LOCK_2 (the writing lock) is being held by another thread. 考虑LOCK_2(写锁)由另一个线程持有。 If we have LOCK_1 on the outer block the currently executing thread can at least process all the reading code before being put into a queue for the writing lock (essentially, the ability to execute code at point A). 如果我们在外部块上有LOCK_1,则当前正在执行的线程可以在被放入写入锁定队列之前至少处理所有读取代码(本质上,是在A点执行代码的能力)。 If we flipped the order of the locks around, the currently executing thread will end up having to wait for the writing is complete, then proceed onto reading and writing whilst holding onto the writing lock (all the way through reading too). 如果我们翻转锁定的顺序,当前正在执行的线程将最终不得不等待写入完成,然后继续读取写入同时保持写入锁定(一直通过读取)。

Another problem that comes up when the order of locks are switched (and not consistently, some code has LOCK_1 first and others have LOCK_2 first). 当切换锁的顺序时出现的另一个问题(并且不一致,一些代码首先具有LOCK_1而其他代码首先具有LOCK_2)。 Consider that two threads both eagerly try to execute code which have different locking orders. 考虑到两个线程都急切地尝试执行具有不同锁定顺序的代码。 Thread 1 obtains LOCK_1 in the outer block and thread 2 obtains LOCK_2 from the outer block. 线程1在外部块中获得LOCK_1,线程2从外部块获得LOCK_2。 Now when thread 1 tries to obtain LOCK_2, it can't since thread 2 has it. 现在,当线程1尝试获取LOCK_2时,它不能,因为线程2拥有它。 And when thread 2 tries to obtain LOCK_1, it can't either because thread 1 has it. 当线程2尝试获取LOCK_1时,它也不能,因为线程1拥有它。 The two threads essentially block on each other forever, forming a deadlock situation. 这两个线程基本上永远相互阻塞,形成死锁情况。

To answer your question, if you want to lock on two objects immediately without doing any sort of processing between locks then the order is irrelevant (essentially no processing at point A or C). 要回答你的问题,如果你想立即锁定两个对象而不在锁之间进行任何处理,那么顺序是无关紧要的(基本上没有在A点或C点处理)。 HOWEVER it is essential to keep the order consistent throughout your program as to avoid deadlocking. 但是 ,在整个程序中保持订单一致至关重要 ,以避免死锁。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM