在学习JAVA线程时候的遇到一个很奇怪的现象。让我们先来看代码
public class TestThread {
public static void main(String[] args) {
IRun ir = new IRun();
Thread it = new Thread(ir);
it.start();
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
Logger.getLogger(TestThread.class.getName()).log(Level.SEVERE, null, ex);
}
ir.setStop();
}
static class IRun implements Runnable {
boolean exec = true;
public void setStop() {
exec = false;
System.out.println("exec = " + exec);
}
@Override
public void run() {
int c = 0;
while (exec) {
c++;
}
System.out.println("退出了循环");
}
}
}
在上述程序首先创建一个Runnable对象ir,然后创建一个线程对象it,通过it.start()启动线程,执行IRun类中的run()函数,执行一个while循环。while循环的条件由布尔变量exec控制。主程序中等待1秒钟后,修改exec的值为false,按照正常的逻辑应该是在exec值为false后,循环结束执行下面输出语句,然后线程结束,程序退出。
但是,上述代码在运行后,将不会执行run()函数中的System.out.println("退出了循环");语句,程序一直保持运行。对这种现象,在帖子”关于JAVA线程,请大神帮忙“中still_rain给出的这种现象的原因是线程访问的同步问题导致,当主线程(main函数所在的线程)中修改变量exec的值后,在创建执行的线程中访问的exec的值没有发生变化(或者说可能是主线程、执行线程这两个线程exec变量是不同的对象,好像是说java会将一些资源在每个线程中复制一份?有待确认)。在exec变量前加上volatile修饰符后,程序运行正常,问题似乎解决。
现在以上述代码为基础,进行一次小小的修改。在run()函数中的while循环中加上一个类对象操作,比如新建一个字符串,则run()函数为
public void run() {
int c = 0;
while (exec) {
String s = new String("");
c++;
}
System.out.println("退出了循环");
}
}
运行程序,程序按预期的顺序执行,顺利结束。这时候不管exec变量是否有volatile修饰符,且只在while循环中存在类对象操作(新建对象、对象函数调用(调用的函数不能返回基本数据类型),以及像System.out.println这样的操作),程序的运行都很正常,对这种现象就不是上述的同步问题能够解释的了。在帖子”关于JAVA线程,请大神帮忙“中still_rain(感谢热情的回复)从编译器优化的角度进行了解释。当while循环中只有简单的基本数据类型参与运算的时候由于执行速度太快,将while循环编译成了while(true)语句。导致循环不会退出,从而循环后的输出不会执行,且程序不会退出。
为了探究这个奇怪现象的原因,让我们看看编译后的字节码,下图是用jclasslib查看的字节码,是修改前的run()函数字节码。
从图中可以看到,循环体从aload_0开始,到goto 2结束。控制跳转的语句是ifeq 15,意思是如果值为0则跳转到15,否则执行下面的语句。
当将上面的while(exec)语句修改为while(true)后,字节码为:
由两图可以看出,编译器并没有将while(exec)语句优化为while(true)。再看修改后的代码编译的字节码:
循环体从aload_0起,到goto 2结束。从ifeq 25下面的一行到astotre_2行,为String s = new String("");语句的字节码,将这段去掉则字节码与修改前的一样。所以基本可以排除编译器优化导致的程序不正常运行。
请看到这篇文章的大神们能够给予指导,探究出现这种情况的原因,谢谢