下面的代码,修改load函数中doSomething(0);传入的值,从0到4,分别会出现能跳出循环和不能跳出循环两种情况
0,什么也不做,不能跳出循环
1,sleep,能跳出循环
2,使用System.out.println输出,能跳出循环
3,等待10微秒,不能跳出循环
4,等待20微秒,能跳出循环
package thread;
public class VisibilityTest {
private boolean flag = true;
public void refresh() {
flag = false;
System.out.println(Thread.currentThread().getName() + "修改flag");
}
public void load() {
System.out.println(Thread.currentThread().getName() + "开始执行...");
int i = 0;
while (flag) {
i ++;
doSomething(0);
}
System.out.println(Thread.currentThread().getName() + "跳出循环:i=" + i);
}
public void doSomething(int x) {
switch (x) {
case 1: // 能跳出循环
try {
Thread.sleep(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
case 2: // 能跳出循环
System.out.println("====="); // synchronized
break;
case 3: // 不能跳出循环
shortWait(10000); // 10微秒
break;
case 4: // 能跳出循环
shortWait(20000); // 20微秒
break;
case 0: // 不能跳出循环
default: // 不能跳出循环// do nothingbreak;
}
}
public static void shortWait(long interval) {
long start = System.nanoTime();
long end;
do {
end = System.nanoTime();
} while (start + interval >= end);
}
public static void main(String[] args) {
VisibilityTest test = new VisibilityTest();
new Thread(() -> test.load(), "threadA").start();
try {
Thread.sleep(2000);
new Thread(() -> test.refresh(), "threadB").start();
} catch (InterruptedException e) {
e.printStackTrace();
}
对于ThreadA,会经过read、load操作将flag读入工作内存(本地内存),然后use操作写入cpu core1(寄存器保存);
对于ThreadB,也会有上面的步骤。但最后是写入cpu core2(因为cpu core1一直在被占用),然后给flag赋值为false,再经过assign操作写回工作内存,注意,此时不会立马执行store、write操作,写回主内存,而是在某一时刻进行;
此时,ThreadA继续读取flag,是从工作内存中加载的,不是从主内存中加载的load函数中doSomething(0);传入的值,从0到4:
• 0,什么也不做,while(flag)一直在进行,threadA的工作内存缓存一直有效,因此一直未从主内存中读取,一直是true;
• 1,sleep,会让出CPU时间片,线程上下文切换(保存现场、恢复现场),因此会从主内存中重新读取flag,读到了false则跳出循环;
注意:与sleep的时间没有关系,即时sleep0ms,也会让出CPU时间片;
• 2,使用System.out.println输出,println方法实现内部有synchronized(this)操作,synchronized会保证可见性,因此会从主内存中读取flag。读到了false则跳出循环;
• 3,等待10微秒,太短了,缓存未失效,因此一直未从主内存中读取,一直是true;
• 4,等到20微秒,缓存失效了,因此会从主内存中读取,读到了false则跳出循环;
以上问题归根到底是可见性问题。当线程2更改了主内存的值后,没有及时的让线程1知道这个值被更改了。
如果给flag变量加上volatile关键词后:
private volatile boolean flag = true;
上述0-4,执行都会跳出循环。
上面例子表示了volatile关键字的含义之一:保证变量对所有线程的可见性(及时性,一但线程1更改了变量,线程2立马知道。底层是通过缓存一致性协议。)。