刚到公司楼下,好友就给我发来了一段程序,问我如何如何???本来问题有好几个,但最后我们都对其中的一个问题感觉不可思议,难以理解。然后深究了很久,终于把问题搞懂了,因此记一笔。代码如下:
import java.util.concurrent.TimeUnit;
public class Vo {
boolean running = true;
void m(){
System.out.println("m start");
while (running){
//System.out.println("m running"); //加上这行代码和不加的结果不同,为什么?
}
System.out.println("m end");
}
public static void main(String[] args) {
Vo v = new Vo();
new Thread(v::m,"t1").start();
try{
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
v.running = false;
}
}
问题点是这样的:在while (running){} 中加一行System.out.println("m running"); 分别有什么结果,造成的原因是什么?
刚开始我一直好奇,不就是加了个println吗?能有什么不同的结果呢!于是我亲手去把代码运行了一下,结果还真是让人大吃一惊,请看下图:
不加代码:System.out.println("m running");,运行后线程进入无线循环,无法打印第10行“m end”。
加上代码:System.out.println("m running");,运行后线程while循环一段时间后,跳出循环打印第10行“m end”。
请问这是为什么?我无法理解,一个小小的sout("m running")就影响到程序的结果了吗,sout在程序中只是用来输出结果到控制台,怎么会这样?到底有什么端倪?
带着这个疑问,我去百度上查,感觉不知道如何表达自己的问题,我太难了!理论上来说,启动线程m后while开始进入无限循环,睡眠1s后running变量修改为false,而此时while应该可以重新读取running为false,会跳出无限循环,打印“m end”结束程序。但是以上的操作完全不在意料之中,于是我就开始去百度从多线程开始查询,这里我就简单的说一下:
Java线程工作内存与主内存变量交换过程
- 每一个线程都有自己私有的工作区,我们称之为工作内存,用来存储当前线程的运行数据。
- JVM中规定所有变量都存储在主内存中,主内存作为共享区域,所以线程皆可访问。
- 线程对变量的操作都必须在工作内存中进行(赋值,读取)。
主内存的变量在工作内存中的值是复制过去的副本,读写完成后刷新主内存,这意味着主内存如果发生了改变,工作内存并无法获得最新的结果。多个线程对一个共享变量进行修改时,都是对自己工作内存的副本进行操作,相互不可见。所以我们可以内存交互的方式去解释上面程序在加上代码:System.out.println("m running"); 后的运行结果,如下:
当main方法启动后,子线程m方法也随之运行,这时主内存中的running = true便刷新到m线程的工作内存中,while代码块进入循环模式并不断打印“m running”,1s后,主内存中的变量runing 被修改为 false,同时刷新到m线程的工作区中,此时while检测到running = false,便跳出循环,打印“m end”结束程序。
那么老问题来了,同样是while循环,为什么没有加代码:System.out.println("m running"); 的程序中的runing变量依然是true呢?这说明主内存中的running = false并没有刷新到对应线程的工作内存中,到底是什么原因导致的呢?这是因为如果没有System.out.println("m running"); while语句的循环调用就会及其频繁,导致对工作内存区的running变量的访问也是同等的频繁,工作区并没有时间间隔去刷新主内存中的running = true到该工作内存中,因此工作内存中始终是running = true;所以程序一直保持无限循环当中。那么是不是while中存在可执行的程序代码就能让程序正常结束呢?答案是不一定,因为这个可执行的代码的执行时间还需要达到一定的限度。而System.out.println("m running"); 就不一样,System.out属于JNI,去调用的时候需要较长的时间(以前刚工作的时候时候,部门老大看见System.out.println("test")这样的测试代码出现就会骂人,现在想起来才知道其中的道理)。如果不信的话,我们
来用i++测试一下:
i++ 的运算属于工作内存中的操作,并不像System.out涉及到接口的调用,因此每次运算时间极短,时间间隔不足以刷新工作内存。
在用TimeUnit.SECONDS.sleep(1);测试:
1s的时间间隔足以让工作内存刷新,因此程序正常执行。
写完这个博客,为道日损也学会了不少东西,同时也对主内存和工作内存加深了印象。所以建议大家在学习的路上不要放过细节,多钻研,顺藤摸瓜,你可能就找到了真正的答案。当然,这里还要提到我的一个好友徐文静女士,就是她给我提供的题材,感谢感谢!!!

