在開始以前,咱們先來看如下代碼會有什麼問題?html
public class ThreadStopExample { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { try { System.out.println("子線程開始執行"); // 模擬業務處理 Thread.sleep(1000); } catch (Exception e) { } // 僞代碼:重要的業務方法 System.out.println("子線程的重要業務方法"); }); t1.start(); // 讓子線程先運行一點業務 Thread.sleep(100); // 終止子線程 t1.stop(); // 等待一段時間,確保子線程「執行完」 Thread.sleep(3000); System.out.println("主線程執行完成"); } }
或許你已經發現了,上面這段代碼使用了 Thread.stop()
來終止線程,在 Java 程序中是不容許這樣終止線程的。什麼?你問爲何不能這樣?java
首先來講 IDE 都會鄙視你了,它會阻止你使用 Thread.stop()
!react
什麼?你不信。那麼來看這張圖:
安全
好吧,那爲何不能這樣用呢?總得給我一個敷衍的理由吧?多線程
實際上是這樣的,以文章剛開頭的那段代碼來講,它的執行結果是:app
子線程開始執行less
主線程執行完成ide
咱們發現了一個驚天的大問題,最重要的那段僞代碼居然沒執行,以下圖所示:
測試
能夠看出使用 stop()
終止線程以後,線程剩餘的部分代碼會放棄執行,這樣會形成嚴重的且不易被發現的驚天大 Bug,假如沒有執行的那段代碼是釋放系統資源的代碼,或者是此程序的主要邏輯處理代碼。這就破壞了程序基本邏輯的完整性,致使意想不到的問題發生,並且它還很隱祕,不易被發現和修復。ui
有人說,這還不簡單,我加個 finally
不就完了嗎?
這???槓精哪都有,今年特別多。
行,既然這個說服不了你,咱接着往下看。
咱們知道在 Java 中 synchronized
屬於獨佔式可重入悲觀鎖,若是咱們使用它修飾代碼,妥妥的多線程沒問題,但若是碰到 stop()
方法就不必定了,直接來看代碼吧。
public class ThreadStopExample { public static void main(String[] args) throws InterruptedException { MyThread myThread = new MyThread(); Thread t2 = new Thread(myThread); // 開啓線程 t2.start(); for (int i = 0; i < 10; i++) { Thread t = new Thread(myThread); t.start(); } // 結束線程 t2.stop(); } /** * 自定義原子測試線程 */ static class MyThread implements Runnable { // 計數器 int num = 0; @Override public void run() { // 同步代碼塊,保證原子操做 synchronized (MyThread.class) { // 自增 num++; try { // 線程休眠 0.1 秒 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } // 自減 num--; System.out.println(Thread.currentThread().getName() + " | num=" + num); } } } }
以上程序的執行結果爲:
Thread-5 | num=1
Thread-4 | num=1
Thread-2 | num=1
Thread-1 | num=1
Thread-8 | num=1
Thread-6 | num=1
Thread-9 | num=1
Thread-3 | num=1
Thread-7 | num=1
Thread-10 | num=1
從結果能夠看出,以上代碼通過 synchronized
修飾的 ++ 和 -- 操做,到最後打印的結果 num 居然不是 0,而是 1。
這是由於 stop()
方法會釋放此線程中的全部鎖,致使程序執行紊亂,破壞了程序的原子操做邏輯。
以上的這些問題,致使了 JDK 廢棄了 stop()
的方法,它的廢棄源碼以下:
/** * Forces the thread to stop executing. * <p> * If there is a security manager installed, its <code>checkAccess</code> * method is called with <code>this</code> * as its argument. This may result in a * <code>SecurityException</code> being raised (in the current thread). * <p> * If this thread is different from the current thread (that is, the current * thread is trying to stop a thread other than itself), the * security manager's <code>checkPermission</code> method (with a * <code>RuntimePermission("stopThread")</code> argument) is called in * addition. * Again, this may result in throwing a * <code>SecurityException</code> (in the current thread). * <p> * The thread represented by this thread is forced to stop whatever * it is doing abnormally and to throw a newly created * <code>ThreadDeath</code> object as an exception. * <p> * It is permitted to stop a thread that has not yet been started. * If the thread is eventually started, it immediately terminates. * <p> * An application should not normally try to catch * <code>ThreadDeath</code> unless it must do some extraordinary * cleanup operation (note that the throwing of * <code>ThreadDeath</code> causes <code>finally</code> clauses of * <code>try</code> statements to be executed before the thread * officially dies). If a <code>catch</code> clause catches a * <code>ThreadDeath</code> object, it is important to rethrow the * object so that the thread actually dies. * <p> * The top-level error handler that reacts to otherwise uncaught * exceptions does not print out a message or otherwise notify the * application if the uncaught exception is an instance of * <code>ThreadDeath</code>. * * @exception SecurityException if the current thread cannot * modify this thread. * @see #interrupt() * @see #checkAccess() * @see #run() * @see #start() * @see ThreadDeath * @see ThreadGroup#uncaughtException(Thread,Throwable) * @see SecurityManager#checkAccess(Thread) * @see SecurityManager#checkPermission * @deprecated This method is inherently unsafe. Stopping a thread with * Thread.stop causes it to unlock all of the monitors that it * has locked (as a natural consequence of the unchecked * <code>ThreadDeath</code> exception propagating up the stack). If * any of the objects previously protected by these monitors were in * an inconsistent state, the damaged objects become visible to * other threads, potentially resulting in arbitrary behavior. Many * uses of <code>stop</code> should be replaced by code that simply * modifies some variable to indicate that the target thread should * stop running. The target thread should check this variable * regularly, and return from its run method in an orderly fashion * if the variable indicates that it is to stop running. If the * target thread waits for long periods (on a condition variable, * for example), the <code>interrupt</code> method should be used to * interrupt the wait. * For more information, see * <a href="{@docRoot}/../technotes/guides/concurrency/threadPrimitiveDeprecation.html">Why * are Thread.stop, Thread.suspend and Thread.resume Deprecated?</a>. */ @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()); }
能夠看出 stop()
方法被 @Deprecated
註釋修飾了,而被此註解修飾的代碼表示爲過期方法,不建議被使用。從 stop()
的備註信息能夠看出,官方也不建議使用 stop()
,說它是一個非安全的方法。
那如何終止線程呢?這裏提供 2 個正確的方法:
interrupt()
方法終止線程。咱們能夠自定義一個布爾變量來標識是否須要退出線程,實現代碼以下:
// 自定義退出標識退出線程 static class FlagThread extends Thread { public volatile boolean exit = false; public void run() { while (!exit) { // 執行正常的業務邏輯 } } }
能夠看出咱們使用了關鍵字 volatile
對線程進行了修飾,這樣就能夠保證多線程的執行安全了,在咱們須要讓線程退出時,只須要把變量 exit
賦值爲 true
就能夠了。
當咱們使用 interrupt()
方法時,以上兩個示例的執行結果就正常了,執行代碼以下:
public class ThreadStopExample { public static void main(String[] args) throws InterruptedException { // 問題一:破壞了程序的完整性 Thread t1 = new Thread(() -> { try { System.out.println("子線程開始執行"); // 模擬業務處理 Thread.sleep(1000); } catch (Exception e) { } // 僞代碼:重要業務方法 System.out.println("子線程的重要業務方法"); }); t1.start(); // 讓子線程先運行一點業務 Thread.sleep(100); // 終止子線程 t1.interrupt(); // 等待一段時間,確保子線程「執行完」 Thread.sleep(3000); System.out.println("主線程執行完成"); // 問題二:破壞了原子邏輯 MyThread myThread = new MyThread(); Thread t2 = new Thread(myThread); // 開啓線程 t2.start(); for (int i = 0; i < 10; i++) { Thread t = new Thread(myThread); t.start(); } // 結束線程 t2.interrupt(); } /** * 自定義原子測試線程 */ static class MyThread implements Runnable { // 計數器 int num = 0; @Override public void run() { // 同步代碼塊,保證原子操做 synchronized (MyThread.class) { // 自增 num++; try { // 線程休眠 0.1 秒 Thread.sleep(100); } catch (InterruptedException e) { System.out.println(e.getMessage()); } // 自減 num--; System.out.println(Thread.currentThread().getName() + " | num=" + num); } } } }
以上程序的執行結果爲:
子線程開始執行
子線程的重要業務方法
主線程執行完成
sleep interrupted
Thread-1 | num=0
Thread-9 | num=0
Thread-10 | num=0
Thread-7 | num=0
Thread-6 | num=0
Thread-5 | num=0
Thread-4 | num=0
Thread-2 | num=0
Thread-3 | num=0
Thread-11 | num=0
Thread-8 | num=0
能夠看出以上的執行都符合咱們的預期,這纔是正確的終止線程的方式。
本文咱們講了線程的三種終止方式,自定義退出標識的方式、使用 stop()
的方式或 interrupt()
的方式。其中 stop()
的方式會致使程序的完整性和原子性被破壞的問題,而且此方法被 JDK 標識爲過時方法,不建議使用,而 interrupt()
方法無疑是最適合咱們的終止線程的方式。