笔者是一个普通大学大二非计算机专业的学生,在学习java多线程问题的时候出于某种原因碰见了一个难以解决的问题。之所以说是难以解决,是因为它既不是逻辑问题,也不是语法问题。笔者对java的认识并不深刻,特此将这个问题拿上来同各路大牛分享,希望能得到一个满意的答复,先谢过各路大牛~
笔者有长时间在贴吧潜水的经历,懂得提问的规矩,为了帮助大家理解我遇到的问题,我特地写了测试程序(如下),并把我的一些挣扎和思考的过程意义列举出来。
测试程序如下:
运行环境:JDK1.6
package TestThread;
public class TestThread {
//定义Flag,Flag = true,分线程空转,Flag = false,分线程执行输出
private static boolean Flag = false;
//分线程
class MyThread implements Runnable{
@Override
public void run(){
while(true){
System.out.println("分线程debug语句");
while(!Flag){
for(int i = 0;i < 200;i++)
System.out.println("分线程输出");
}
}
}
}
//主线程
public static void main(String[] args){
TestThread tt = new TestThread();
//启动分线程
MyThread mt = tt.new MyThread();
Thread t = new Thread(mt);
t.start();
//主线程sleep3s
System.out.println("让分线程输出3s");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("让分线程空转3s");
Flag = true;
//主线程sleep3s
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("让分线程输出");
Flag = false;
System.out.println(t.isAlive());
}
}
测试程序描述的是两个线程并发的过程,思路比较明确,不多赘述。笔者通过主线程给分线程传递不同的信号量(Flag = true or false),使分线程在Flag = true时什么也不做,输出debug语句,在Flag = false时,输出内层while循环的语句。
主线程执行如下功能:1、激活分线程,Flag = false;2、三秒之后,Flag = true;3、三秒之后,Flag = false。得到的运行结果和预期一样:分线程先输出内层循环语句,三秒空转,然后再输出内层循环语句。
然后问题来了。笔者手贱,将System.out.println("分线程debug语句");一行给注释掉了。
代码如下:
package TestThread;
public class TestThread {
//定义Flag,Flag = true,分线程空转,Flag = false,分线程执行输出
private static boolean Flag = false;
//分线程
class MyThread implements Runnable{
@Override
public void run(){
while(true){
//System.out.println("分线程debug语句");
while(!Flag){
for(int i = 0;i < 200;i++)
System.out.println("分线程输出");
}
}
}
}
//主线程
public static void main(String[] args){
TestThread tt = new TestThread();
//启动分线程
MyThread mt = tt.new MyThread();
Thread t = new Thread(mt);
t.start();
//主线程sleep3s
System.out.println("让分线程输出3s");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("让分线程空转3s");
Flag = true;
//主线程sleep3s
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("让分线程输出");
Flag = false;
System.out.println(t.isAlive());
}
}
然后笔者得到的执行结果是:三秒分线程先输出内层循环语句,三秒空转,然后再输出内层循环语句。当主线程再将Flag = false时,分线程却没有如期地再次输出内层循环(笔者亲验)。
笔者不相信自己的眼睛,于是调用了isAlive函数去看看分线程是否还活着,结果返回true,分线程确实还存在,但是却不工作了。笔者才疏学浅,无法解释这一现象,但是在和伙伴的交流讨论中,得出了如下两种推论。这两种可能有一种是正确的,也有可能都是错的,现在都列出来,请各路大牛批评指正:
1、分线程内循环在某个时刻接受到true(不工作)时,java虚拟机认为该线程做的是无用功,虽然分线程还活着,但是不再为其分配CPU,即使是分线程后来又可以正常工作了。
2、分线程内循环在某个时刻接受到true(不工作)时,java虚拟机认为该线程做的是无用功,自己给代码做了优化,莫名其妙就变成这个样子了。
第二种观点是我的小伙伴提出来的。刚开始提出来的时候我认为他说的跟我第一种的想法其实是一个意思,他说不一样。他认为我的想法(第一种)是在运行时发生的,而他的想法是代码在编译阶段已经产生了分歧,即两段代码虽然只是一句System.out之差,编译的结果却是大不相同的。为了说服我,他(大牛)给我导出了两段代码各自的字节码作比较。然而我并不能看懂……我也不知道如何导出字节码,所以字节码就不上代码了,感兴趣的大牛辛苦一下哈,可以自己试试导出看看……
以上就是我遇到的问题。这是笔者提问的处女贴,希望各路大牛不吝赐教,在此先谢过了!
------解决思路----------------------
这里面的问题其实比较复杂,表面上看这段代码没有问题,实际上大有问题。
多线程这块曾经有人说过,初级程序员认为多线程很难,中级程序员认为多线程很简单,高级程序员又认为多线还是很难。
你的这段代码在不同的虚拟机环境上可能会有不同的结果,你可以看看java内存模型相关的文章。
要解决你的问题其实也很简单,对Flag属性加上volatile修饰,或者对Flag的set、get方法使用synchronized进行同步,然后不直接访问Flag,而是通过set、get方法调用就可以了。