搞過Java線程的人都知道,stop這個方法是臭名昭著了,早就被棄用了,但是現在任然有很多鍾情與他的人,永遠都放不下他,因為從他的字面意思上我們可以知道他貌似可以停止一個線程,這個需求是每個搞線程開發的人都想要的操作,但是他並非是真正意義上的停止線程,而且停止線程還會引來一些其他的麻煩事,下面就來詳細的介紹一下這個方法的歷史:
從SUN的官方文檔可以得知,調用Thread.stop()方法是不安全的,這是因為當調用Thread.stop()方法時,會發生下面兩件事:
1. 即刻拋出ThreadDeath異常,在線程的run()方法內,任何一點都有可能拋出ThreadDeath Error,包括在catch或finally語句中。
2. 會釋放該線程所持有的所有的鎖,而這種釋放是不可控制的,非預期的。
當線程拋出ThreadDeath異常時,會導致該線程的run()方法突然返回來達到停止該線程的目的。ThreadDetath異常可以在該線程run()方法的任意一個執行點拋出。但是,線程的stop()方法一經調用線程的run()方法就會即刻返回嗎?
packagecom.threadstop.demo;
publicclassThreadStopTest {
publicstaticvoidmain(String[] args) {
try{
Thread t = newThread() {
//對於方法進行了同步操作,鎖對象就是線程本身
publicsynchronizedvoidrun() {
try{
longstart=System.currentTimeMillis();
//開始計數
for(inti =0; i <100000; i++)
System.out.println("runing.."+ i);
System.out.println((System.currentTimeMillis()-start)+"ms");
} catch(Throwable ex) {
System.out.println("Caught in run: "+ ex);
ex.printStackTrace();
}
}
};
//開始計數
t.start();
//主線程休眠100ms
Thread.sleep(100);
//停止線程的運行
t.stop();
} catch(Throwable t) {
System.out.println("Caught in main: "+ t);
t.printStackTrace();
}
}
}
運行結果如下:
由於打印的數據太多了,就沒有全部截圖了,但是我們可以看到,調用了stop方法之后,線程並沒有停止,而是將run方法執行完。那這個就詭異了,多次運行之后發現每次運行的結果都表明,工作線程並沒有停止,而是每次都成功的數完數(執行完run方法),然后正常中止,而不是由stop()方法進行終止的。這個是為什么呢?根據SUN的文檔,原則上只要一調用thread.stop()方法,那么線程就會立即停止,並拋出ThreadDeath error,查看了Thread的源代碼后才發現,原先Thread.stop(Throwable obj)方法是同步的,而我們工作線程的run()方法也是同步,那么這樣會導致主線程和工作線程共同爭用同一個鎖(工作線程對象本身),由於工作線程在啟動后就先獲得了鎖,所以無論如何,當主線程在調用t.stop()時,它必須要等到工作線程的run()方法執行結束后才能進行,結果導致了上述奇怪的現象。
下面看一下stop的源碼:
@Deprecated
publicfinalvoidstop() {
stop(newThreadDeath());
}
再進到stop看:
@Deprecated
publicfinalsynchronizedvoidstop(Throwable obj) {
if(obj ==null)
thrownewNullPointerException();
SecurityManager security = System.getSecurityManager();
if(security !=null) {
checkAccess();
if((this!= Thread.currentThread()) ||
(!(obj instanceofThreadDeath))) {
security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
}
}
// A zero status value corresponds to "NEW", it can't change to
// not-NEW because we hold the lock.
if(threadStatus !=0) {
resume(); // Wake up thread if it was suspended; no-op otherwise
}
// The VM can handle all thread states
stop0(obj);
}
stop0(obj)是一個native方法,我們看到stop方法是同步的,而這個同步的鎖對象正好也是線程本身,所以造成上面的現象。
把上述工作線程的run()方法的同步去掉,再進行執行,結果就如上述第一點描述的那樣了,運行結果如下:
從結果中我們可以看到,調用stop方法會拋出一個ThreadDeath異常,這時候run方法也就執行結束了,線程就終止了,這種是用拋異常來結束線程的,但是這種拋出線程是不安全的,因為他不可控制,不知道到在run方法中的何處就可能拋出異常,所以是危險的。下面在看一下stop的這個隱患可能造成的影響:
接下來是看看當調用thread.stop()時,被停止的線程會不會釋放其所持有的鎖,看如下代碼:
publicstaticvoidmain(String[] args) {
//定義鎖對象
finalObject lock =newObject();
//定義第一個線程,首先該線程拿到鎖,而后等待3s,之后釋放鎖
try{
Thread t0 = newThread() {
publicvoidrun() {
try{
synchronized(lock) {
System.out.println("thread->"+ getName() +" acquire lock.");
sleep(3*1000);
System.out.println("thread->"+ getName() +" 等待3s");
System.out.println("thread->"+ getName() +" release lock.");
}
} catch(Throwable ex) {
System.out.println("Caught in run: "+ ex);
ex.printStackTrace();
}
}
};
//定義第二個線程,等待拿到鎖對象
Thread t1 = newThread() {
publicvoidrun() {
synchronized(lock) {
System.out.println("thread->"+ getName() +" acquire lock.");
}
}
};
//線程一先運行,先拿到lock
t0.start();
//而后主線程等待100ms,為了做延遲
Thread.sleep(100);
//停止線程一
//t0.stop();
//這時候在開啟線程二
t1.start();
} catch(Throwable t) {
System.out.println("Caught in main: "+ t);
t.printStackTrace();
}
}
運行結果如下:
從運行結果中我們可以看到,當沒有進行t0.stop()方法的調用時, 可以發現,兩個線程爭用鎖的順序是固定的。這個現象是正常的。
下面我們把t0.stop注釋的哪行,刪除注釋,調用t0.stop()方法,運行結果如下:
從運行結果中我們可以看到,調用了t0.stop()方法后,可以發現,t0線程拋出了ThreadDeath error並且t0線程釋放了它所占有的鎖。
從上面的程序驗證結果來看,thread.stop()確實是不安全的。它的不安全主要是:釋放該線程所持有的所有的鎖。一般任何進行加鎖的代碼塊,都是為了保護數據的一致性,如果在調用thread.stop()后導致了該線程所持有的所有鎖的突然釋放(不可控制),那么被保護數據就有可能呈現不一致性,其他線程在使用這些被破壞的數據時,有可能導致一些很奇怪的應用程序錯誤。
下面順便說一下:
Java中多線程鎖釋放的條件:
1)執行完同步代碼塊,就會釋放鎖。(synchronized)
2)在執行同步代碼塊的過程中,遇到異常而導致線程終止,鎖也會被釋放。(exception)
3)在執行同步代碼塊的過程中,執行了鎖所屬對象的wait()方法,這個線程會釋放鎖,進入對象的等待池。(wait)
從上面的三點我就可以看到stop方法釋放鎖是在第二點的,通過拋出異常來釋放鎖,通過證明,這種方式是不安全的,不可靠的。
好吧,Thread中的stop方法就說到這里了,后續還會有關Thread中的一些方法的詳解,一定要關注奧!