这个例子很简单,新建的线程里有一个普通变量stop,用来表示是否结束循环里的自增操作。主线程启动这个线程后,将该变量置为true,观察线程 是否打印出finish loop那行,如果存在可见性问题,主线程修改stop值为true,线程v看stop的值应该还是false。
01 class VisibilityThread extends Thread { 02 private boolean stop; 03 04 public void run() { 05 int i = 0; 06 System.out.println("start loop."); 07 while(!getStop()) { 08 i++; 09 } 10 System.out.println("finish loop,i=" + i); 11 } 12 13 public void stopIt() { 14 stop = true; 15 } 16 17 public boolean getStop(){ 18 return stop; 19 } 20} 21 22 public class VisibilityTest { 23 public static void main(String[] args) throws Exception { 24 VisibilityThread v = new VisibilityThread(); 25 v.start(); 26 27 Thread.sleep(1000);//停顿1秒等待新启线程执行 28 System.out.println("即将置stop值为true"); 29 v.stopIt(); 30 Thread.sleep(1000); 31 System.out.println("finish main"); 32 System.out.println("main中通过getStop获取的stop值:" + v.getStop()); 33 } 34}我们先来执行一遍(操作系统:XP,下同。JDK:见图示):
执行结果如上图,有人该问了,线程v最终停下来了,这不是表示它看到stop值为true了吗?是的,确实如此。但让我们再看一个这个程序的执行结果。
这一次,我们发现程序一直未能结束,表示线程v看到stop的值是false,但是主线程打印出的值却是true。
对比两次的执行方式,我们发现后一次加上了-server选项。显示version的时候也由Client VM变成了Server VM。那么Client VM与Server VM有什么区别在哪里?简单地讲,Client VM启动时做了一般优化,耗时少,启动快,但程序执行的也相对也较慢;Server VM启动的时候做了更多优化,耗时多,启动慢,但程序执行快。如果在运行java命令的时候没有指定具体模式的时候,会有一个默认值,这个默认值随硬件和 操作系统的不同而不同,这里有张JDK 1.6在各平台默认VM模式的图。
我们再来看个例子,这个例子源于hotspot VM的一个bug:
01 public class InterruptedVisibilityTest { 02 public void think() { 03 System.out.println("新线程正在执行"); 04 while (true) { 05 if (checkInterruptedStatus()) break; 06 } 07 System.out.println("新线程退出循环"); 08 } 09 10 private boolean checkInterruptedStatus() { 11 return Thread.currentThread().isInterrupted(); 12 } 13 14 public static void main(String[] args) throws Exception { 15 final InterruptedVisibilityTest test = new InterruptedVisibilityTest(); 16 Thread thinkerThread = new Thread("Thinker") { 17 public void run() { 18 test.think(); 19 } 20 }; 21 thinkerThread.start(); 22 Thread.sleep(1000);//等待新线程执行 23 System.out.println("马上中断thinkerThread"); 24 thinkerThread.interrupt(); 25 System.out.println("已经中断thinkerThread"); 26 thinkerThread.join(3000); 27 if (thinkerThread.isAlive()) { 28 System.err.println("thinkerThread未能在中断后3s停止"); 29 System.err.println("JMV bug"); 30 System.err.println("主线程中检测thinkerThread的中断状态:" + thinkerThread.isInterrupted()); 31 } 32 } 33}这个例子也很简单,thinkerThread一直检查中断状态,主线程在启动thinkerThread之后的某个时刻调用interrupt中 断thinkerThread。在《The Java Language Specification Java SE 7 Edition》§17.4.4中我们能够看到,如果线程1调用线程2的interrupt方法,那么所有线程(包括线程2)都能通过Thread.isInterrupted方法来检测到这个中断状态。这里直接用hotspot VM的-server模式执行一下,结果如下图:
thinkerThread没能退出循环,没看到主线程所置的中断状态。
后面这个例子是hotpost VM的一个bug导致的,在最新的hotspot中应该已经被修复了(笔者未测试最新版)。其它VM如IBM J9,JRockit,harmony等并没有发现这样的bug。说这是bug,是因为JLS中规定了main发出的中断必须对 thinkerThread可见。但是,如第一个例子,则不是bug,因为JLS是允许这种行为的。当在第一个例子的循环中的i++后面加上一句 Thread.yield()调用(该调用在规范中并没有特殊内存语义),这我使用的这个版本的VM上,就看不到可见性问题了。这也说明,JVM的优化是无法预知的,允许可见性的地方不一定就真会出现或一直出现。
JLS允许未充分同步的代码出现可见性问题,但是某个实际的JVM完全可以实现的比JLS上规定的更强,比如不允许可见性问题出现,那么,在这样的 JVM上就展现不出这样的问题了。第一个例子这里只是运行在hotpost下,也许在其它JVM下同样采用最优化的方式执行,可能并不会出现这里的问题。
在我们编码的时候,也许并不知道代码会跑在什么样的系统上,不知道会采用什么样的JVM,为了使得写出的代码更健壮,我们只能按照规范所规定的最低 保证去编码,要避免这类问题,只有保证代码充分同步,避免数据争用,而不应该依赖于某个具体JVM实现。即使是具体的某款JVM,不同的版本间也可能存在 着差异。
最后,这样的例子启发我们,测试代码的时候应尽可能启用各JVM的最佳优化模式。
至此,我们已经了解到实际中多线程运行真的会出现这样的场景。为什么会出现可见性问题?有什么解决方案?下面链接中的内容为我们提供了专业的解答。
http://ifeve.com/volatile/http://ifeve.com/java-memory-model-0/http://ifeve.com/java-concurrency-constructs/上有人发的一个真实案例文章转自 并发编程网-ifeve.com