實現多線程的官方正確方法: 2 種。java
Oracle 官網的文檔說明面試
方法小結多線程
方法一: 實現 Runnable 接口。架構
方法二: 繼承 Thread 類。ide
代碼示例工具
/** * <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 接口)更好。code
方法二的缺點:對象
從代碼的架構去考慮,具體執行的任務也就是 run 方法中的內容,它應該和線程的建立、運行的機制也就是 Thread 類是解耦的。因此不該該把他們混爲一談。從解耦的角度,方法一更好。blog
該方法每次若是想新建一個任務,只能去新建一個獨立的線程,而新建一個獨立的線程這樣的損耗是比較大的,它須要去建立、而後執行,執行完了還要銷燬;而若是使用 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 對象也不會執行它。
總結
方法一: 實現 Runnable 接口的 run 方法,並把 Runnable 實例傳給 Thread 類,再讓 Thread 類去執行這個 run 方法。
方法二: 重寫 Thread 的 run 方法(繼承 Thread 類)。
」線程池建立線程也算是一種新建線程的方式。「
線程池建立線程代碼示例
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 中)
分析
」經過 Callable 和 FutureTask 建立線程,也算是一種新建線程的方式。「
類圖展現
分析
」無返回值是實現 Runnable 接口,有返回值是實現 Callable 接口,因此 Callable 是新的實現線程的方式。「
定時器是新的實現線程的方式。
定時器實現線程代碼示例
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 不支持多繼承的角度。(對於擴展性而言)
若有寫的不足的,請見諒,請你們多多指教。