1、為什么廢棄Thread的stop函數?
對于有多線程開發經驗的開發者,應該大多數在開發過程中都遇到過這樣的需求,就是在某種情況下,希望立即停止一個線程。
比如:做Android App開發,當打開一個界面時,需要開啟線程請求網絡獲取界面的數據,但有時候由于網絡特別慢,用戶沒有耐心等待數據獲取完成就將界面關閉,此時就應該立即停止線程任務,不然一般會內存泄露,造成系統資源浪費,如果用戶不斷地打開又關閉界面,內存泄露會累積,最終導致內存溢出,APP閃退。
可能有不少開發者用過Thread的stop去停止線程,當然此函數確實能停止線程,不過JAVA官方早已將它廢棄,不推薦使用,這是為什么?
- stop是通過立即拋出ThreadDeath異常,來達到停止線程的目的,此異常拋出有可能發生在任何一時間點,包括在catch、finally等語句塊中,但是此異常并不會引起程序退出(筆者只測試了Java8)。
- 由于有異常拋出,導致線程會釋放全部所持有的鎖,極可能引起線程安全問題。
由于以上2點,stop這種方式停止線程是不安全的。
下面是stop的源碼(Java8):
@Deprecated
public final void stop() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
checkAccess();
if (this != Thread.currentThread()) {
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(new ThreadDeath());
}
private native void stop0(Object o);
上述源碼中關鍵代碼就是stop0(new ThreadDeath())函數,這是Native函數,傳遞的參數是ThreadDeath,ThreadDeath是一個異常對象,該對象從Native層拋到了Java層,從而導致線程停止,不過此異常并不會引起程序退出。
很多時候為了保證數據安全,線程中會編寫同步代碼,如果當線程正在執行同步代碼時,此時調用stop,引起拋出異常,導致線程持有的鎖會全部釋放,此時就不能確保數據的安全性,出現無法預期的錯亂數據,還有可能導致存在需要被釋放的資源得不到釋放,引發內存泄露。所以用stop停止線程是不推薦的。
2、用Thread的interrupt結束線程
其實調用Thread對象的interrupt函數并不是立即中斷線程,只是將線程中斷狀態標志設置為true,當線程運行中有調用其阻塞的函數(Thread.sleep,Object.wait,Thread.join等)時,阻塞函數調用之后,會不斷地輪詢檢測中斷狀態標志是否為true,如果為true,則停止阻塞并拋出InterruptedException異常,同時還會重置中斷狀態標志;如果為false,則繼續阻塞,直到阻塞正常結束。
因此,可以利用這種中斷機制來控制結束線程的運行。只要理解機制,代碼的實現其實比較簡單。
2.1、結束未使用阻塞函數的線程
public class Main {
public static void main(String[] args) {
InnerClass innerClass = new InnerClass();
Thread thread = new Thread(innerClass);
thread.start();
long i = System.currentTimeMillis();
while (System.currentTimeMillis() - i < 10 * 1000) {
thread.isAlive();
}
thread.interrupt();
}
static class InnerClass implements Runnable {
@Override
public void run() {
System.err.println("start work");
while (!Thread.currentThread().isInterrupted()) {
System.out.println("doing work");
}
System.err.println("done work");
}
}
}
思路其實就是用isInterrupted來判斷線程是否處于中斷狀態,若是中斷狀態,則跳出正在執行的任務,使線程結束運行。
2.2、結束使用阻塞函數的線程
public class Main {
public static void main(String[] args) {
InnerClass innerClass = new InnerClass();
Thread thread = new Thread(innerClass);
thread.start();
long i = System.currentTimeMillis();
while (System.currentTimeMillis() - i < 10 * 1000) {
thread.isAlive();
}
thread.interrupt();
}
static class InnerClass implements Runnable {
@Override
public void run() {
System.err.println("start work");
while (!Thread.currentThread().isInterrupted()) {
System.out.println("doing work");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
System.err.println("done work");
}
}
}
思路同2.1,需要注意的是,調用sleep函數觸發InterruptedException異常時,在catch代碼塊中需調用interrupt函數,使線程再次處于中斷狀態,使while循環條件為false,使線程跳出循環,結束運行。若不調用,while循環為死循環,線程無法結束。
2.3、關于Thread的靜態函數interrupted與Thread的對象函數isInterrupted
先對比下2函數的源碼:
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
return isInterrupted(false);
}
/**
* Tests if some Thread has been interrupted. The interrupted state
* is reset or not based on the value of ClearInterrupted that is
* passed.
*/
private native boolean isInterrupted(boolean ClearInterrupted);
從源碼中可以看出,2函數都是調用了Native函數private native boolean isInterrupted(boolean ClearInterrupted);,前者調用傳的參數為true,所以,調用interrupted函數,會在檢測線程中斷狀態標志是否為true后,還會將中斷狀態標志重置為false。而isInterrupted函數只是檢測線程中斷狀態標志。






