2

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

但是,添加一个锁对象和一个synchronizedNOT 语句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不完全优化掉?)

4

2 回答 2

3

VM 无法推断锁没有被其他线程同步,因此无法对其进行优化。

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

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

没有完成锁定省略可能是有原因的。优化对于使用本地等的代码非常有用VectorStringBuffer但这仅适用于旧代码;很长一段时间没有人这样做。

一些代码甚至可能依赖于更强大的 pre-java-5 模型,在这种模型中,任何锁都不能被忽略。可能有许多程序,类似于 OP 精心制作的示例,在新模型中不正确,但在过去已经工作了多年。锁省略可能会破坏这些程序。

于 2013-01-15T04:46:45.003 回答
2

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

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

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

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

于 2013-01-15T08:22:35.440 回答