首页 » 编写高质量代码:改善Java程序的151个建议 » 编写高质量代码:改善Java程序的151个建议全文在线阅读

《编写高质量代码:改善Java程序的151个建议》建议119:启动线程前stop方法是不可靠的

关灯直达底部

有这样一个案例,我们需要一个高效率的垃圾邮件制造机,也就是需要有尽可能多的线程来尽可能多地制造垃圾邮件,垃圾邮件需要的信息保存在数据库中,如收件地址、混淆后的标题、反垃圾处理后的内容等,垃圾制造机的作用就是从数据库中读取这些信息,判断是否符合条件(如收件地址必须包含@符号、标题不能为空等),然后转换成一份真实的邮件发送出去。

整个应用逻辑很简单,这必然是一个多线程的应用,垃圾邮件制造机需要继承Thread类,代码如下:


//垃圾邮件制造机

class SpamMachine extends Thread{

@Override

public void run(){

//制造垃圾邮件

System.out.println("制造大量垃圾邮件……");

}

}


在客户端代码中需要发挥计算机的最大潜能来制造邮件,也就是说开尽量多的线程,这里我们使用一个while循环来处理,代码如下:


public static void main(Stringargs){

//不分昼夜地制造垃圾邮件

while(true){

//多线程多个垃圾邮件制造机

SpamMachine sm=new SpamMachine();

//条件判断,不符合条件就设置该线程不可执行

if(!false){

sm.stop();

}

//如果线程是stop状态,则不会启动

sm.start();

}

}


在此段代码中,设置了一个极端条件:所有的线程在启动前都执行stop方法,虽然它是一个已过时(Deprecated)的方法,但它的运行逻辑还是正常的,况且stop方法在此处的目的并不是停止一个线程,而是设置线程为不可启用状态。想来这应该是没有问题的,但是运行结果却出现了奇怪的现象:部分线程还是启动了,也就是在某些线程(没有规律)中的start方法正常执行了。在不符合判断规则的情况下,不可启用状态的线程还是启用了。这是为什么呢?

这是线程启动(start方法)的一个缺陷。Thread类的stop方法会根据线程状态来判断是终结线程还是设置线程为不可运行状态,对于未启动的线程(线程状态为NEW)来说,会设置其标志位为不可启动,而其他的状态则是直接停止。stop方法的源代码如下:


public final void stop(){

if((threadStatus!=0)&&!isAlive()){

return;

}

stop1(new ThreadDeath());

}

private final synchronized void stop1(Throwable th){

/*安全检查省略*/

if(threadStatus!=0){

resume();

stop0(th);

}else{

if(th==null){

throw new NullPointerException();

}

stopBeforeStart=true;

throwableFromStop=th;

}


这里设置了stopBeforeStart变量,标志着是在启动前设置了停止标志,在start方法中是这样校验的:


public synchronized void start(){

//分配栈内存,启动线程,运行run方法

start0();

//在启动前设置了停止状态

if(stopBeforeStart){

stop0(throwableFromStop);

}

}

//本地方法

private native void start0();


注意看start0方法和stop0方法的顺序,start0方法在前,也就是说即使stopBeforeStart为true(不可启动),也会先启动一个线程,然后再stop0结束这个线程,而罪魁祸首就在这里!

明白了原因,我们的情景代码也就很容易修改了,代码如下:


public static void main(Stringargs){

//不分昼夜的制造垃圾邮件

while(true){

//条件判断,不符合条件就不创建线程

if(!false){

//多线程多个垃圾邮件制造机

new SpamMachine().start();

}

}

}


不再使用stop方法进行状态的设置,直接通过判断条件来决定线程是否可启用。对于start方法的该缺陷,一般不会引起太大的问题,只是增加了线程启动和停止的精度而已。