# CPUCacheTest 更多的讨论 CPUCacheTest 经常作为大厂的面试题,难倒很多精通多线程并发的面试者,这里列出这个题目尽可能多的答案。综合考虑了变量可见性,线程上下文切换,以及JIT优化。 如下一段程序,期望当主线程A设置stop变量为true的时候,线程B退出循环。 但实际上线程B看不到更新后的值,从而一直循环下去。 ~~~java public class CPUCacheTest { private static boolean stop = false; public static void main(String[] args){ Thread a = new Thread("B"){ public void run(){ while (!stop) { int a = 1; } System.out.println("exit"); } }; a.start(); pause(100); //停止标记,但未能停止线程B stop = true; } public static void pause(int time){ try { TimeUnit.MILLISECONDS.sleep(time); }catch(Exception ex){ } } } ~~~ ## 原因分析 在第三章已经说过,多线程下变量的可见性,需要添加`volatile` 关键字,保证stop变量能被其他线程看到 ```java private static volatile boolean stop = false; ``` ## 场景一 判断如下代码是否也能保证线程B退出 ```java Thread a = new Thread("B"){ public void run(){ while (!stop) { System.out.println("in loop"); } System.out.println("exit"); } }; ``` 答案,能退出,因为方法`out.println()`实际上如下实现,synchronized保证变量可见性 ```java private void write(String s) { synchronized (this) { ..... } } ``` ## 场景二 判断如下代码是否也能保证线程B退出,调用pause方法,休眠1毫秒 ```java Thread a = new Thread("B"){ public void run(){ while (!stop) { pause(1); } System.out.println("exit"); } }; ``` 答案,能退出,pause方法引起线程休眠,再次唤醒的时候,上下文切换,线程B能得到stop最新的变量 ## 场景三 判断如下代码是否也能保证线程B退出,定义int类型的变量b,在循环里自增 ``` public class CPUCacheTest { private static /* volatile */ boolean stop = false; static int b = 1; public static void main(String[] args){ Thread a = new Thread("B"){ public void run(){ while (!stop) { b++ } System.out.println("exit "+b); } }; System.out.println("start"); a.start(); pause(100); stop = true; } ``` 答案,不能退出,没有任何原因触发变量可见性 ## 场景四 判断如下代码是否也能保证线程B退出,定义Integer类型的变量b,在循环里自增 ```java public class CPUCacheTest { private static /* volatile */ boolean stop = false; static Integer b = 1; public static void main(String[] args){ Thread a = new Thread("B"){ public void run(){ while (!stop) { b++ } System.out.println("exit "+b); } }; System.out.println("start"); a.start(); pause(100); stop = true; } ``` 答案,能退出,因为如上代码`b++` 实际的操作如下。 ```java int temp = b.intValue(); temp++; b = new Integer(temp); ``` 因为Integer定义如下,因为变量是final,保证了变量可见性 ```java private final int value; public Integer(int value) { this.value = value; } ``` ## 场景五 判断如下代码是否也能保证线程B退出,while循环里定义Integer 变量,并自增 ```java public class CPUCacheTest { private static /* volatile */ boolean stop = false; public static void main(String[] args){ Thread a = new Thread("B"){ public void run(){ while (!stop) { Integer c = 1; c++; } System.out.println("exit "+b); } }; System.out.println("start"); a.start(); pause(100); stop = true; } ``` 答案,不能退出,在while循环里,变量c的操作被JIT优化成如下代码 ```java Thread a = new Thread("B"){ public void run(){ while (!stop) { //代码被消除 } System.out.println("exit "+b); } }; ``` ## 场景六 判断如下代码是否也能保证线程B退出,while循环里定义Integer 变量c,自增后赋值给b ```java public class CPUCacheTest { private static /* volatile */ boolean stop = false; static int b = 1; public static void main(String[] args){ Thread a = new Thread("B"){ public void run(){ while (!stop) { Integer c = 1; c++; b=c; } System.out.println("exit "+b); } }; System.out.println("start"); a.start(); pause(100); stop = true; } ``` 答案,不能退出,变量c 赋值到b操作会提取到循环外执行,这涉及到JIT编译器优化,表达式提升(hoisting),运行代码如下 ```java Thread a = new Thread("B"){ public void run(){ Integer c = 1; c++; b=c; while (!stop) { } System.out.println("exit "+b); } }; ``` ## 场景七 判断如下代码是否也能保证线程B退出,while循环里使用StringBuffer ```java Thread a = new Thread("B"){ public void run(){ while (!stop) { StringBuffer sb = new StringBuffer(); sb.append("abc"); } System.out.println("exit "+b); } }; ``` 答案,能退出,StringBuffer 的append方法有关键字synchronzied ```java @Override public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; } ``` ## 场景八 同场景3,但运行这个程序加上虚拟机参数 -Xint ,程序能正确退出么? ```java public class CPUCacheTest { private static /* volatile */ boolean stop = false; static int b = 1; public static void main(String[] args){ Thread a = new Thread("B"){ public void run(){ while (!stop) { b++ } System.out.println("exit "+b); } }; System.out.println("start"); a.start(); pause(100); stop = true; } ``` 在第8章JIT说过,-Xint 是让Java解释执行,这样,不会涉及到变量可见性,因此无论while循环里是什么,都能退出