讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議119:啟動線程前stop方法是不可靠的 >

建議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方法的該缺陷,一般不會引起太大的問題,只是增加了線程啟動和停止的精度而已。