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”?
如果 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 相对于稍后顺序获取同一锁对象的字节码。