目錄
- 詳細分析 Java 中實作多執行緒的方法有幾種?(從本質上出發)
- 正確的說法(從本質上出發)
- 經典錯誤說法(從本質上出發)
- 常見面試問題
詳細分析 Java 中實作多執行緒的方法有幾種?(從本質上出發)
正確的說法(從本質上出發)
-
實作多執行緒的官方正確方法: 2 種,
-
Oracle 官網的檔案說明

-
方法小結
-
方法一: 實作 Runnable 介面,
-
方法二: 繼承 Thread 類,
-
-
代碼示例
/** * <p> * 實作 Runnable 介面的方式創建執行緒 * </p> * * @author 踏雪彡尋梅 * @version 1.0 * @date 2020/9/7 - 00:34 * @since JDK1.8 */ public class RunnableStyle implements Runnable { @Override public void run() { System.out.println("用 Runnable 方式實作執行緒~~~"); } public static void main(String[] args) { Thread thread = new Thread(new RunnableStyle()); thread.start(); } }/** * <p> * 繼承 Thread 類的方式創建執行緒 * </p> * * @author 踏雪彡尋梅 * @version 1.0 * @date 2020/9/7 - 00:37 * @since JDK1.8 */ public class ThreadStyle extends Thread { @Override public void run() { System.out.println("用 Thread 方式實作執行緒~~~"); } public static void main(String[] args) { new ThreadStyle().start(); } } -
兩種方式的對比
-
方法一(實作 Runnable 介面)更好,
-
方法二的缺點:
-
從代碼的架構去考慮,具體執行的任務也就是 run 方法中的內容,它應該和執行緒的創建、運行的機制也就是 Thread 類是解耦的,所以不應該把他們混為一談,從解耦的角度,方法一更好,
-
該方法每次如果想新建一個任務,只能去新建一個獨立的執行緒,而新建一個獨立的執行緒這樣的損耗是比較大的,它需要去創建、然后執行,執行完了還要銷毀;而如果使用 Runnable 介面的方式,我們就可以利用執行緒池之類的工具,利用這些工具就可以大大減小這些創建執行緒、銷毀執行緒所帶來的損耗,所以方法一相比于方法二的這一點,好在資源的節約上,
-
繼承了 Thread 類之后,由于 Java 不支持雙繼承,那么這個類就無法繼承其他的類了,這大大限制了我們的可擴展性,
-
-
-
兩種方式的本質區別
-
方法一: 最終呼叫
target.run;,通過以下兩圖可以知道使用這個方法時實際上是傳遞了一個 target 物件,執行了這個物件的 run 方法,

-
方法二: run() 整個都被重寫,一旦子類重寫了父類的方法,原有方法會被覆寫被拋棄,即以下代碼不會被這次呼叫所采納,

-
綜上,兩種方法都是執行了 run 方法,只不過 run 方法的來源不同,
-
-
同時使用兩種方法會怎樣?
-
代碼演示
/** * <p> * 同時使用 Runnable 和 Thread 兩種實作執行緒的方式 * </p> * * @author 踏雪彡尋梅 * @version 1.0 * @date 2020/9/7 - 22:38 * @since JDK1.8 */ @SuppressWarnings("all") public class BothRunnableThread { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println("我來自 Runnable,,,"); } }) { @Override public void run() { System.out.println("我來自 Thread,,,"); } }.start(); } } -
運行結果

-
分析
-
首先創建了一個匿名內部類 Thread,傳入了一個 Runnable 物件,
-
然后重寫了 Thread 的 run 方法,最后啟動執行緒,
-
因為重寫了 Thread 的 run 方法,所以它父類的 run 方法就被覆寫掉了,所以即便傳入了 Runnable 物件也不會執行它,
-
-
-
總結
- 從以上的分析中,準確的講創建執行緒只有一種方式那就是構造 Thread 類,而實作執行緒的執行單元有兩種方式(run 方法的兩種不同實作情況),
-
方法一: 實作 Runnable 介面的 run 方法,并把 Runnable 實體傳給 Thread 類,再讓 Thread 類去執行這個 run 方法,
-
方法二: 重寫 Thread 的 run 方法(繼承 Thread 類),
-
- 從以上的分析中,準確的講創建執行緒只有一種方式那就是構造 Thread 類,而實作執行緒的執行單元有兩種方式(run 方法的兩種不同實作情況),
經典錯誤說法(從本質上出發)
-
”執行緒池創建執行緒也算是一種新建執行緒的方式,“
-
執行緒池創建執行緒代碼示例
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * <p> * 執行緒池創建執行緒的方法 * </p> * * @author 踏雪彡尋梅 * @version 1.0 * @date 2020/9/7 - 23:05 * @since JDK1.8 */ public class ThreadPools { public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 1000; i++) { // 添加任務 executorService.submit(new Task() {}); } } } class Task implements Runnable { @Override public void run() { try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); } } -
執行緒池創建執行緒原始碼(DefaultThreadFactory 中)

-
分析
- 通過執行緒池中的原始碼,可以知道執行緒池創建執行緒的方式本質上也是通過構造 Thread 的方式創建的,所以執行緒池創建執行緒的本質和上文中是一樣的,所以不能簡單的認為執行緒池也是一種新的創建執行緒的方式,
-
-
”通過 Callable 和 FutureTask 創建執行緒,也算是一種新建執行緒的方式,“
-
類圖展示


-
分析
- 從類圖中可以知道這兩個創建執行緒的本質也是和之前的一樣的,
-
-
”無回傳值是實作 Runnable 介面,有回傳值是實作 Callable 介面,所以 Callable 是新的實作執行緒的方式,“
- 和第 2 點差不多,
-
定時器是新的實作執行緒的方式,
-
定時器實作執行緒代碼示例
import java.util.Timer; import java.util.TimerTask; /** * <p> * 定時器創建執行緒 * </p> * * @author 踏雪彡尋梅 * @version 1.0 * @date 2020/9/7 - 23:48 * @since JDK1.8 */ public class DemoTimmerTask { public static void main(String[] args) { Timer timer = new Timer(); timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }, 1000, 1000); } } -
分析
- 和前面幾點一樣,定時器創建執行緒的方法最終本質也離不開上文中的那兩類方法,
-
-
匿名內部類和 Lambda 運算式的方式創建執行緒是新的創建執行緒方式,
-
實際上也和前面幾點一樣是一個表面現象,本質上還是那兩種方法,
-
使用方式代碼示例
/** * <p> * 匿名內部類創建執行緒 * </p> * * @author 踏雪彡尋梅 * @version 1.0 * @date 2020/9/7 - 23:54 * @since JDK1.8 */ public class AnonymousInnerClassDemo { public static void main(String[] args) { // 第一種 new Thread() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }.start(); // 第二種 new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }).start(); } }/** * <p> * Lambda 運算式創建執行緒 * </p> * * @author 踏雪彡尋梅 * @version 1.0 * @date 2020/9/7 - 23:58 * @since JDK1.8 */ public class LambdaDemo { public static void main(String[] args) { new Thread(() -> System.out.println(Thread.currentThread().getName())).start(); } } -
運行結果


-
- 總結
- 多執行緒的實作方式,在代碼中寫法千變萬化,但其本質萬變不離其宗,
常見面試問題
-
有多少種實作執行緒的方法?思路有 5 點,
-
從不同的角度看,會有不同的答案,
-
典型答案是兩種,這兩種方式的對比,
-
從原理來看,兩種本質都是一樣的(都是實作 run 方法),
-
具體展開說其他方式(代碼的實作上的不同方式,原理還是基于那兩個本質),
-
將以上幾點做結論,
-
-
實作 Runnable 介面和繼承 Thread 類哪種方式更好?
-
從代碼架構角度,(應該去解耦,兩件事情:1.具體的任務即 run 方法中的內容;2.和執行緒生命周期相關的如創建執行緒、運行執行緒、銷毀執行緒即 Thread 類去做的事情)
-
新建執行緒的損耗的角度,(繼承 Thread 類,需要新建執行緒、執行完之后還要銷毀,實作 Runnable 介面的方式可以反復的利用同一個執行緒,比如執行緒池就是這么做的,用于執行緒生命周期的損耗就減少了)
-
Java 不支持多繼承的角度,(對于擴展性而言)
-
如有寫的不足的,請見諒,請大家多多指教,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/132220.html
標籤:Java
