Java 中,在编写多线程程序时对可能发生的语句重新排序。在这篇文章中,我们将重点介绍两个线程之间的变量可见性以及更改共享变量时会发生什么。
代码检查
让我们检查以下代码片段:
import java.util.Date;
public class UnSynchronizedCountDown {
private int number = Integer.MAX_VALUE;
public Thread countDownUntilAsync(final int threshold) {
return new Thread(() -> {
while (number>threshold) {
number--;
System.out.println("Decreased "+number +" at "+ new Date());
}
});
}
private void waitUntilThresholdReached(int threshold) {
while (number>threshold) {
}
}
public static void main(String[] args) {
int threshold = 2125840327;
UnSynchronizedCountDown unSynchronizedCountDown = new UnSynchronizedCountDown();
unSynchronizedCountDown.countDownUntilAsync(threshold).start();
unSynchronizedCountDown.waitUntilThresholdReached(threshold);
System.out.println("Threshold reached at "+new Date());
}
}
这是一段糟糕的代码:两个线程在没有任何同步的情况下对同一个变量号进行操作。现在代码可能会永远运行下去!无论线程何时countDown
达到目标,主线程都不会选择低于阈值的新值。这是因为对变量所做的更改number
尚未对主线程可见。因此,这不仅涉及同步和发出线程安全操作,还涉及确保线程所做的更改可见。
可见性和同步
Java 中的内部锁定保证一个线程可以看到另一个线程的更改。因此,当我们使用synchronized时,一个线程的更改对于遇到synchronized块的另一个线程是可见的。
让我们改变我们的例子并展示这一点:
package com.gkatzioura.concurrency.visibility;
public class SynchronizedCountDown {
private int number = Integer.MAX_VALUE;
private String message = "Nothing changed";
private static final Object lock = new Object();
private int getNumber() {
synchronized (lock) {
return number;
}
}
public Thread countDownUntilAsync(final int threshold) {
return new Thread(() -> {
message = "Count down until "+threshold;
while (number>threshold) {
synchronized (lock) {
number--;
if(number<=threshold) {
}
}
}
});
}
private void waitUntilThresholdReached(int threshold) {
while (getNumber()>threshold) {
}
}
public static void main(String[] args) {
int threshold = 2147270516;
SynchronizedCountDown synchronizedCountDown = new SynchronizedCountDown();
synchronizedCountDown.countDownUntilAsync(threshold).start();
System.out.println(synchronizedCountDown.message);
synchronizedCountDown.waitUntilThresholdReached(threshold);
System.out.println(synchronizedCountDown.message);
}
}
对数字变量的访问受锁保护。修改该变量也使用同一锁进行同步。
最终,程序将按预期终止,因为我们将达到阈值。每次我们进入同步块时,倒计时线程所做的更改都将对主线程可见。这不仅适用于同步块中涉及的变量,也适用于倒计时线程可见的变量。因此,尽管消息变量在程序结束时不在任何同步块内,但其更改的值已公开,因此可以看到打印的正确值。