1. 程式人生 > >Java多執行緒併發01——執行緒的建立與終止,你會幾種方式

Java多執行緒併發01——執行緒的建立與終止,你會幾種方式

> 本文開始將開始介紹 Java 多執行緒與併發相關的知識,多謝各位一直以來的關注與支援。關注我的公眾號「Java面典」瞭解更多 Java 相關知識點。 # 執行緒的建立方式 在 Java 中,使用者常用的主動建立執行緒的方式有三種,分別是 **繼承 Thread 類**、**實現 Runnable 介面** 、**通過Callable 和 Future**。 ## 繼承 Thread 類 * 定義 Thread 類的子類,並重寫該類的 run 方法; * 呼叫執行緒物件的 start() 方法來啟動該執行緒。 **通過繼承 Thread 實現的執行緒類,多個執行緒間無法共享執行緒類的例項變數(需要建立不同 Thread 物件)。** ```java /** * 通過繼承Thread實現執行緒 */ public class MyThread extends Thread { public void run() { System.out.println("MyThread.run()"); } } MyThread myThread = new MyThread(); myThread.start(); ``` ## 實現 Runnable 介面 * 如果自己的類已經 extends 另一個類,就無法直接 extends Thread,此時,可以實現一個 Runnable 介面; * 呼叫執行緒物件的start()方法來啟動該執行緒。 ```java /** * 通過實現Runnable介面實現的執行緒類 */ public class RunnableTest implements Runnable { @Override public void run() { System.out.println("RunnableTest.run()"); } public static void main(String[] args) { RunnableTest runnableTest = new RunnableTest() ; Thread thread = new Thread(runnableTest); thread.start(); } } ``` ## 通過 Callable、Future 從 Thread 和 Runnable 兩種方式可以看出,兩種方式都**不支援返回值**,且**不能宣告丟擲異常**。 而 Callable 介面則實現了此兩點,Callable 介面如同 Runable 介面的升級版,其提供的 call() 方法將作為執行緒的執行體,同時允許有返回值。 但是 Callable 物件不能直接作為 Thread 物件的 target,我們可以使用 FutureTask 類來包裝 Callable 物件,該 FutureTask 物件封裝了該 Callable 物件的 call() 方法的返回值。 ```java import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class CallableTest { public static void main(String[] args) { CallableTest callableTest = new CallableTest() ; //因為Callable介面是函式式介面,可以使用Lambda表示式 FutureTask task = new FutureTask((Callable)()->{ System.out.println("FutureTask and Callable"); return "hello word"; }); try{ System.out.println("子執行緒返回值 : " + task.get()); } catch (Exception e){ e.printStackTrace(); } } } ``` # 執行緒的終止方式 執行緒除了正常結束外,還可以通過特定方式終止執行緒,終止執行緒常用的方式有以下三種:**使用退出標誌退出執行緒**、** Interrupt 方法結束執行緒**、**stop 方法終止執行緒**。 ## 使用退出標誌退出執行緒 最常使用的方式其實現方式是:定義一個 boolean 型的標誌位,線上程的 run() 方法中根據這個標誌位是 true 還是 false 來判斷是否退出,這種情況一般是將任務放在 run() 方法中的一個 while 迴圈中執行的。 ```java public class ThreadSafe extends Thread { public volatile boolean exit = false; public void run() { while (!exit){ //do work } } public static void main(String[] args) throws Exception { ThreadFlag thread = new ThreadFlag(); thread.start(); sleep(5000); // 主執行緒延遲5秒 thread.exit = true; // 終止執行緒thread thread.join(); System.out.println("執行緒退出!"); } } ``` ## Interrupt 方法結束執行緒 使用 interrupt() 方法來中斷執行緒有兩種情況: 1. **執行緒處於阻塞狀態**。如使用了 sleep,同步鎖的 wait,socket 中的 receiver,accept 等方法時,會使執行緒處於阻塞狀態。`使用 interrupt 方法結束執行緒的時候,一定要先捕獲 InterruptedException 異常之後通過 break 來跳出迴圈,才能正常結束 run 方法。` ```java public class ThreadInterrupt extends Thread { public void run() { try { sleep(50000); // 延遲50秒 } catch (InterruptedException e) { System.out.println(e.getMessage()); } } public static void main(String[] args) throws Exception { Thread thread = new ThreadInterrupt(); thread.start(); System.out.println("在50秒之內按任意鍵中斷執行緒!"); System.in.read(); thread.interrupt(); thread.join(); System.out.println("執行緒已經退出!"); } } ``` 2. **執行緒未處於阻塞狀態**。使用 isInterrupted() 判斷執行緒的中斷標誌來退出迴圈。當使用 interrupt() 方法時,中斷標誌就會置 true,和使用自定義的標誌來控制迴圈是一樣的道理。 ```java public class ThreadSafe extends Thread { public void run() { while (!isInterrupted()) { //非阻塞過程中通過判斷中斷標誌來退出 try { Thread.sleep(5*1000);//阻塞過程捕獲中斷異常來退出 } catch (InterruptedException e) { e.printStackTrace(); break;//捕獲到異常之後,執行 break 跳出迴圈 } } } } ``` ## stop 方法終止執行緒 使用 stop 方法可以強行終止正在執行或掛起的執行緒。我們可以使用如下的程式碼來終止執行緒: ```java thread.stop(); ``` **採用 stop 是不安全的**,主要影響點如下: 1. thread.stop() 呼叫之後,建立子執行緒的執行緒就會丟擲 ThreadDeatherror 的錯誤; 2. 呼叫 stop 會釋放子執行緒所持有的所有鎖。導致了該執行緒所持有的所有鎖的突然釋放(不可控制),那麼被保護資料就有可能呈現不一致性。 # 總結 * **執行緒建立**:推薦使用 Runnable 或者 Callable 方式建立執行緒,相比繼承,介面實現可以更加靈活,不會受限於Java的單繼承機制。 * **執行緒終止**:執行緒終止推薦使用 標誌位 或 Interrupt 方式終止,stop 方式對執行緒不安全,易導致資料不