同步锁之外的Java内存可见性

问题描述 投票:0回答:1

synchronized锁是否保证下面的代码总是打印'END'?

public class Visibility {

    private static int i = 0;

    public static void main(String[] args) throws InterruptedException {

        Thread thread_1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(i);
                while (true) {
                    if (i == 1) {
                        System.out.println("END");
                        return;
                    }
                    synchronized (Visibility.class){}
                }
            }
        });
        thread_1.start();
        Thread.sleep(1000);
        synchronized (Visibility.class) {
            i = 1;
        }
    }
}

我在我的笔记本电脑上运行它,它总是打印“END”,但我想知道JVM是否能保证这段代码总是打印“END”?

java multithreading synchronized memory-barriers memory-visibility
1个回答
1
投票

根据 JLS §17.4.5:在订单之前发生

如果 hb(x, y) 和 hb(y, z),则 hb(x, z)。

换句话说,HB(Happens-before)是及物性的。 HB 是可观察性的主要参与者:如果

hb(x, y)
,则不可能观察到 Y 行中的状态,因为它在 X 行执行之前 - 而这正是您想要做的(或者更确切地说,防止发生):您对 Y 行 (
if (i == 1)
) 是否可以观察到 X 行之前的状态(
i = 1
,在代码片段底部的同步块中)感兴趣。

鉴于传递性规则,答案是否定的:

  • hb(exiting a synchronized block, entering it)
    是一件事。不同线程获取监视器是一个有序的事件,它们之间存在HB关系。因此,线程中倒数第二个
    }
    (退出块)1和零语句
    synchronized
    块之间存在 hb 关系。

  • 如果内部线程随后以某种方式运行(奇怪,因为这意味着它需要更多超过1秒才能启动,但从技术上讲,JVM没有时间保证,所以理论上是可能的),

    i
    已经是1 . 可能此更改尚未“同步”,除非 while 循环命中了无内容
    synchronized
    块,然后该块建立了 hb,从而强制
    i
    的可见性变为
    1

  • 如果内部线程之前运行(所有情况下都是 100%,其中的

    Thread.sleep(1000)
    几乎如此),则最终将应用相同的逻辑。

  • 为了实际将其加在一起,我们需要添加“自然”hb 规则:如果 X 和 Y 由同一线程执行,并且 Y 按程序顺序位于 X 之后,则字节码 X 和 Y 建立

    hb(x, y)
    。即给定:
    y = 5; System.out.println(y)
    你无法观察到 y 在
    y = 5;
    运行之前的样子 - 这就是“废话!” HB 规则 - 当然,如果 JVM 可以随意在单个线程中重新排序内容,那么 java 作为一种语言将毫无用处。这+传递性规则就足够了。

这里有一个技术问题

您启动的线程永远不会自愿放弃,因此甚至可能不会达到可能造成严重破坏的保存点。你永远不应该编写像这样忙着旋转的代码,JVM 并不特别清楚事情是如何工作的,这会导致 CPU 出现大量的热量/功率问题!在单核处理器上,本质上允许 JVM 将所有时间都花在忙等待上,永远,这是使 JVM 永远不打印END的一种方法:因为 main将

i
设置为 1
never
的线程会抽出时间来处理它。这打破了线程的一般要点。 synchronized引入了一个保存点,因此它最终会被抢占,但这可能需要相当长的时间。在正确的硬件上,它可能需要比一秒长得多的时间。
通过在繁忙循环中推入某种 

sleep

wait
或使用
j.u.concurrent
锁/信号量/等来轻松修复。

[1] 从技术上讲,HB 是 JVM 事务,适用于字节码。然而,那个右大括号确实有一个等效的字节码(大多数右大括号没有;那个有):它是监视器的释放(“释放”
synchronized

锁)。这正是 HB 相对于稍后顺序获取同一锁对象的字节码。

    

© www.soinside.com 2019 - 2024. All rights reserved.