简体   繁体   English

线程之间,同步和提升之间可变变量的一致性

[英]Consistency of mutable variables between threads, synchronized, and hoisting

On my machine the following code runs indefinitely (Java 1.7.0_07): 在我的机器上,以下代码无限期运行(Java 1.7.0_07):

public static void main(String[] args) throws InterruptedException {
    Thread backgroundThread = new Thread(new Runnable() {
        public void run() {
            int i = 0;
            while (!stopRequested) {
                i++;
            }
        }
    });
    backgroundThread.start();
    TimeUnit.SECONDS.sleep(1);
    stopRequested = true;
}

However, add a single lock object and a single synchronized statement NOT around stopRequested (in fact, nothing occurs in the synchronized block), and it terminates: 但是,不要在stopRequested周围添加单个锁对象和单个synchronized语句(实际上,在同步块中什么也没有发生),并且它将终止:

public static void main(String[] args) throws InterruptedException {
    Thread backgroundThread = new Thread(new Runnable() {
        public void run() {
            Object lock = new Object();
            int i = 0;
            while (!stopRequested) {
                synchronized (lock) {}
                i++;
            }
        }
    });
    backgroundThread.start();
    TimeUnit.SECONDS.sleep(1);
    stopRequested = true;
}

In the original code, the variable stopRequested is "hoisted", becoming: 在原始代码中,变量“ stopRequested被“悬挂”,变为:

if (!stopRequested)
    while (true)
        i++;

However, in the modified version, it seems this optimization is not occurring, why? 但是,在修改后的版本中,似乎没有进行这种优化,为什么? (In fact, why is synchronized not optimized away entirely?) (实际上,为什么synchronized没有完全优化掉?)

VM is unable to reason that the lock is not synchronized by other threads, so it cannot be optimized away. VM无法推断出该锁没有被其他线程同步,因此无法对其进行优化。

Per Java Memory Model, all synchronization blocks are totally ordered, and this order (on the same lock) helps to establish happens-before relation. 对于每个Java内存模型,所有同步块都是完全有序的,并且此顺序(在同一锁上)有助于建立先发生后关联。 That's why VM can't remove a synchronization block; 这就是VM无法删除同步块的原因。 unless VM can prove that only one thread is ever synchronizing on an object, then all these sync blocks can be removed with no impact on happens-before relation. 除非VM可以证明只有一个线程在对象上进行过同步,否则可以删除所有这些同步块,而不会影响事前发生的关系。

If the lock is a local object, VM could do escape analysis to elide the lock. 如果锁是本地对象,则VM可以进行转义分析以消除该锁。 We've been hearing about escape analysis for years, but as the example shows, and as I've tested not very long ago, it doesn't seems to be working yet. 多年来,我们一直在听到有关逃逸分析的信息,但是正如示例所示,正如我不久前进行的测试所示,它似乎还没有奏效。

There might be a reason why lock elision isn't being done. 锁省略未完成可能是有原因的。 The optimization is great for code that uses local Vector or StringBuffer etc. But that's only in old codes; 对于使用本地VectorStringBuffer等的代码,此优化非常有用。 nobody does that for a long time. 很长时间没有人这样做。

Some code might even depend on the stronger pre-java-5 model, in which no lock can be elided ever. 某些代码甚至可能依赖于更强大的java-5之前的模型,在该模型中,任何锁都无法消除。 There might be many programs, similar to OP's crafted example, that are incorrect in the new model, but have been working for years in the past. 可能有很多程序(类似于OP的精心制作的示例)在新模型中均不正确,但已使用了多年。 Lock elision may break these programs. 锁定省略可能会破坏这些程序。

While this might look like a memory visibility issue, it is usually a JIT optimisation issue in simple examples. 尽管这看起来像是内存可见性问题,但在简单示例中通常是JIT优化问题。 This is because the JIT can detect whether you are modifying the flag in that thread can inline it if you don't. 这是因为JIT可以检测到您是否正在修改该线程中的标志,因此可以内联它。 Effectively turning it into an infinite loop. 有效地将其变成无限循环。

One way you can tell this is that visibility issues are short lived, usually too short for you to see. 您可以这样说的一种方式是,可见性问题是短暂存在的,通常对于您来说太短了。 While they are random they are usually one micro-second to a milli-second. 尽管它们是随机的,但通常为一微秒到一毫秒。 ie until the thread context switches and when it runs again it doesn't keep the old value with it. 也就是说,直到线程上下文切换并且再次运行时,它才会保留旧值。 The fact you can see examples where it consistently turned into an infinite loop which never "detects" the change is a give away. 您可以看到一些示例,在该示例中,始终如一地变成了一个永不“察觉”更改的无限循环,这是一个礼物。

If you just slow down the loop with a Thread.sleep(10) this can prevent it running long enough to be compiled. 如果仅使用Thread.sleep(10)减慢循环速度,则可能会阻止循环运行足够长时间以进行编译。 It has to loop 10,000+ times to be optimised. 它必须循环10,000+次才能进行优化。 This usually "fixes" the problem. 这通常可以“解决”问题。

Adding thread safety code such as using a volatile variable or adding a synchronized block can prevent optimisation from being made. 添加线程安全代码(例如使用易失性变量或添加同步块)可能会导致无法进行优化。

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

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