1、為什么廢棄Thread的stop函數(shù)?
對于有多線程開發(fā)經(jīng)驗(yàn)的開發(fā)者,應(yīng)該大多數(shù)在開發(fā)過程中都遇到過這樣的需求,就是在某種情況下,希望立即停止一個線程。
比如:做Android App開發(fā),當(dāng)打開一個界面時,需要開啟線程請求網(wǎng)絡(luò)獲取界面的數(shù)據(jù),但有時候由于網(wǎng)絡(luò)特別慢,用戶沒有耐心等待數(shù)據(jù)獲取完成就將界面關(guān)閉,此時就應(yīng)該立即停止線程任務(wù),不然一般會內(nèi)存泄露,造成系統(tǒng)資源浪費(fèi),如果用戶不斷地打開又關(guān)閉界面,內(nèi)存泄露會累積,最終導(dǎo)致內(nèi)存溢出,APP閃退。
可能有不少開發(fā)者用過Thread的stop去停止線程,當(dāng)然此函數(shù)確實(shí)能停止線程,不過JAVA官方早已將它廢棄,不推薦使用,這是為什么?
- stop是通過立即拋出ThreadDeath異常,來達(dá)到停止線程的目的,此異常拋出有可能發(fā)生在任何一時間點(diǎn),包括在catch、finally等語句塊中,但是此異常并不會引起程序退出(筆者只測試了Java8)。
- 由于有異常拋出,導(dǎo)致線程會釋放全部所持有的鎖,極可能引起線程安全問題。
由于以上2點(diǎn),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);
上述源碼中關(guān)鍵代碼就是stop0(new ThreadDeath())函數(shù),這是Native函數(shù),傳遞的參數(shù)是ThreadDeath,ThreadDeath是一個異常對象,該對象從Native層拋到了Java層,從而導(dǎo)致線程停止,不過此異常并不會引起程序退出。
很多時候?yàn)榱吮WC數(shù)據(jù)安全,線程中會編寫同步代碼,如果當(dāng)線程正在執(zhí)行同步代碼時,此時調(diào)用stop,引起拋出異常,導(dǎo)致線程持有的鎖會全部釋放,此時就不能確保數(shù)據(jù)的安全性,出現(xiàn)無法預(yù)期的錯亂數(shù)據(jù),還有可能導(dǎo)致存在需要被釋放的資源得不到釋放,引發(fā)內(nèi)存泄露。所以用stop停止線程是不推薦的。
2、用Thread的interrupt結(jié)束線程
其實(shí)調(diào)用Thread對象的interrupt函數(shù)并不是立即中斷線程,只是將線程中斷狀態(tài)標(biāo)志設(shè)置為true,當(dāng)線程運(yùn)行中有調(diào)用其阻塞的函數(shù)(Thread.sleep,Object.wait,Thread.join等)時,阻塞函數(shù)調(diào)用之后,會不斷地輪詢檢測中斷狀態(tài)標(biāo)志是否為true,如果為true,則停止阻塞并拋出InterruptedException異常,同時還會重置中斷狀態(tài)標(biāo)志;如果為false,則繼續(xù)阻塞,直到阻塞正常結(jié)束。
因此,可以利用這種中斷機(jī)制來控制結(jié)束線程的運(yùn)行。只要理解機(jī)制,代碼的實(shí)現(xiàn)其實(shí)比較簡單。
2.1、結(jié)束未使用阻塞函數(shù)的線程
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"); } } }
思路其實(shí)就是用isInterrupted來判斷線程是否處于中斷狀態(tài),若是中斷狀態(tài),則跳出正在執(zhí)行的任務(wù),使線程結(jié)束運(yùn)行。
2.2、結(jié)束使用阻塞函數(shù)的線程
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,需要注意的是,調(diào)用sleep函數(shù)觸發(fā)InterruptedException異常時,在catch代碼塊中需調(diào)用interrupt函數(shù),使線程再次處于中斷狀態(tài),使while循環(huán)條件為false,使線程跳出循環(huán),結(jié)束運(yùn)行。若不調(diào)用,while循環(huán)為死循環(huán),線程無法結(jié)束。
2.3、關(guān)于Thread的靜態(tài)函數(shù)interrupted與Thread的對象函數(shù)isInterrupted
先對比下2函數(shù)的源碼:
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函數(shù)都是調(diào)用了Native函數(shù)private native boolean isInterrupted(boolean ClearInterrupted);,前者調(diào)用傳的參數(shù)為true,所以,調(diào)用interrupted函數(shù),會在檢測線程中斷狀態(tài)標(biāo)志是否為true后,還會將中斷狀態(tài)標(biāo)志重置為false。而isInterrupted函數(shù)只是檢測線程中斷狀態(tài)標(biāo)志。