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

时间:2013-01-15 04:40:13

标签: java concurrency synchronization

在我的机器上,以下代码无限期运行(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;
}

但是,在synchronized附近添加一个锁定对象和一个stopRequested语句(实际上,同步块中没有任何内容),并且终止:

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;
}

在原始代码中,变量stopRequested被“悬挂”,变为:

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

然而,在修改后的版本中,似乎没有发生这种优化,为什么呢? (事实上​​,为什么synchronized没有完全优化掉?)

2 个答案:

答案 0 :(得分:3)

虚拟机无法推断锁定未被其他线程同步,因此无法对其进行优化。

根据每个Java内存模型,所有同步块都是完全有序的,并且此顺序(在同一个锁上)有助于建立先发生关系。这就是为什么VM无法移除同步块的原因;除非VM可以证明只有一个线程在对象上进行同步,否则可以删除所有这些同步块,而不会影响之前发生的关系。

如果锁是本地对象,VM可以进行转义分析以消除锁。我们多年来一直听说过逃逸分析,但正如我的例子所示,并且在不久之前我已经进行了测试,它似乎还没有发挥作用。

可能有理由不进行锁定省略。优化非常适合使用本地VectorStringBuffer等的代码。但这仅适用于旧代码;很久没有人这样做了。

某些代码甚至可能依赖于更强大的pre-java-5模型,其中无法永远消除锁定。可能有很多程序,类似于OP的精心设计的例子,在新模型中是不正确的,但过去已经工作了多年。 Lock elision可能会破坏这些程序。

答案 1 :(得分:2)

虽然这可能看起来像内存可见性问题,但在简单示例中通常是JIT优化问题。这是因为JIT可以检测您是否正在修改该标志,如果不这样,该线程可以内联它。有效地将其变为无限循环。

您可以告诉我的一个方法是,可见性问题是短暂的,通常太短,您无法看到。虽然它们是随机的,但通常是一微秒到一毫秒。即,直到线程上下文切换并且当它再次运行时,它不会保留旧值。事实上,你可以看到它始终变成一个永远“检测”变化的无限循环的例子,这是一个让步。

如果你只是使用Thread.sleep(10)减慢循环,这可以防止它运行足够长的时间来编译。它必须循环10,000次才能进行优化。这通常会“解决”问题。

添加线程安全代码(例如使用volatile变量或添加synchronized块)可能会阻止优化。