哈嘍,我是狗哥。話很少說,金三銀四,不少同窗立刻就要參加春招了。而多線程確定是面試必問的,開篇以前,問你們一個問題:建立線程到底有幾種方式?前端
相信以上答案不少同窗都能答出來。但它們都是錯誤的,其實建立線程的方式只有一種。爲何?狗哥你丫逗我麼?橫看豎看,至少也得兩種呀。別急,放下刀。且聽我慢慢分析:java
首先是繼承 Thread,建立線程最經典的方法,這種方法很常見啦。剛入門的時候,狗哥寫過不知道多少遍了。它的寫法是這樣的:面試
public class MyThread extends Thread { @Override public void run() { System.out.println("經過集成 Thread 類實現線程"); } } // 如何使用 new MyThread().start()
如代碼所示:繼承 Thread 類,並重寫了其中的 run () 方法,以後直接調用 start() 便可實現多線程。相信上面這種方式你必定很是熟悉,而且常常在工做中使用它們。算法
也是最經常使用的方法,寫法以下:數據庫
public class MyRunnable implements Runnable { @Override public void run() { System.out.println("經過實現 Runnable 方式實現線程"); } } // 使用 // 一、建立MyRunnable實例 MyRunnable runnable = new MyRunnable(); //2.建立Thread對象 //3.將MyRunnable放入Thread實例中 Thread thread = new Thread(runnable); //4.經過線程對象操做線程(運行、中止) thread.start();
如代碼所示,這種方法實際上是定義一個線程執行的任務(run 方法裏面的邏輯)並無建立線程。它首先經過 MyRunnable類實現 Runnable 接口,而後重寫 run () 方法,以後還要把這個實現了 run () 方法的實例傳到 Thread 類中才能夠實現多線程。編程
說完這兩種在工做中最經常使用的,咱們再說說第三種。在 Java 中,咱們建立線程池是這樣的:設計模式
// 10 是核心線程數量 ExecutorService service = Executors.newFixedThreadPool(10);
點進去 newFixedThreadPool 源碼,在 IDEA 中調試,能夠發現它的調用鏈是這樣的:微信
Executors.newFixedThreadPool(10) --> new ThreadPoolExecutor(一堆參數) --> Executors.defaultThreadFactory()
能夠發現最終仍是調用了 Executors.defaultThreadFactory()
方法,而這個方法的源碼是這樣的:數據結構
static class DefaultThreadFactory implements ThreadFactory { // 線程池序號 static final AtomicInteger poolNumber = new AtomicInteger(1); // 線程序號 final AtomicInteger threadNumber = new AtomicInteger(1); // 線程組 final ThreadGroup group; // 線程池前綴 final String namePrefix; DefaultThreadFactory() { SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-"; } /** * 重點方法 * @param r * @return */ @Override public Thread newThread(Runnable r) { Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); // 是不是守護線程 if (t.isDaemon()) { t.setDaemon(false); } // 設置優先級 if (t.getPriority() != Thread.NORM_PRIORITY) { t.setPriority(Thread.NORM_PRIORITY); } return t; } }
如上源碼所示:線程池建立線程本質上是默認經過 DefaultThreadFactory
線程工廠來建立的。它能夠設置線程的一些屬性,好比:是否守護線程、優先級、線程名、等等。多線程
但不管怎麼設置,最終它仍是須要經過 new Thread () 建立線程的。因此線程池建立線程並無脫離以上的兩種基本的建立方式。
第四種是有返回值的 Callable 建立線程,用法是這樣的:
public class MyCallable implements Callable<Integer> { @Override public Integer call() throws Exception { return new Random().nextInt(); } // 使用方法 // 一、建立線程池 ExecutorService service = Executors.newFixedThreadPool(10); // 二、提交任務,並用 Future提交返回結果 Future< Integer > future = service.submit(new MyCallable()); }
Callable 與 Runnable 名字還有點像,區別在於 Runnable 是無返回值的。它們的本質都是定義線程要作的任務(call 或 run 方法裏面的邏輯),而不是說他們自己就是線程。但不管有無返回值,它們都是須要被線程執行。
如代碼所示,它們能夠提交到線程池執行,經過 sumbit 方法提交。這時就參考方式三,由線程工廠負責建立線程。固然,還有其餘方法執行 Callable 任務。可是無論怎麼說,它仍是離不開實現 Runnable 接口和繼承 Thread 類這兩種方式。
咱們使用 Timer 的方式以下:
public class MyTimer { public static void main(String[] args) { timer(); } /** * 指定時間 time 執行 schedule(TimerTask task, Date time) */ public static void timer() { Timer timer = new Timer(); // 設定指定的時間time,此處爲2000毫秒 timer.schedule(new TimerTask() { public void run() { System.out.println("執行定時任務"); } }, 2000); } }
如代碼所示,Timer 定時器在兩秒以後執行一些任務,它也確實建立了線程,可是深刻源碼:
private final TimerThread thread = new TimerThread(queue); public Timer() { this("Timer-" + serialNumber()); } public Timer(String name) { thread.setName(name); thread.start(); } class TimerThread extends Thread { // 省略內部方法 }
注意到 TimerThread ,它仍是繼承於 Thread ,因此 Timer 建立線程最後又繞回到最開始說的兩種方式了。
有同窗可能說,狗哥你這扯半天不仍是兩種方式麼?我答對了呀。。。別急,容我喝口水,下面分析爲什麼說它是一種?
注意到 Thread 類中有一個 run 方法:
private Runnable target; @Override public void run() { if (target != null) { target.run(); } }
先看實現 Runnable 方式,它啓動線程仍是須要調用 start 方法(由於是 Native 方法咱們看不到具體邏輯),可是線程要執行任務必須仍是要調用 run 方法(否則線程執行的是啥?)。
咱們看代碼,run 方法很是簡單。它判斷 target 不爲 null 就直接執行 target 的 run 方法。而 target 正是咱們實現的 Runnable ,使用 Runnable 接口實現線程時傳給 Thread 類的對象。
在看繼承 Thread 方式,它調用 thread.start(),最終調用的仍是 run 方法(run() 裏面是任務)。只不過這個 run() 是咱們已經重寫的 run() 而不是上面 Runnable(target) 的 run()。
看到這裏可算明白了,事實上建立線程本質只有一種方式,就是構造一個 Thread 類,這是建立線程的惟一方式,不一樣的只是 run 方法(執行內容)的實現方式。
一、2 兩種方式它們的不一樣點僅僅在於實現線程執行內容的不一樣,那麼運行內容來自於哪裏呢?
本質上,實現線程只有一種方式,而要想實現線程執行的內容,卻有兩種寫法:
而後把咱們想要執行的代碼傳入,讓線程去執行。在此基礎上,若是咱們還想有更多實現線程的方式,好比線程池、Callable 以及 Timer 定時器,只須要在此基礎上進行封裝便可。
答案是:Runnable 寫法。
這個很少說,Java 是單繼承。若是使用繼承 Thread 的寫法。將不利於後續擴展。
用 Runnable 負責定義 run() 方法(執行內容)。這種狀況下,它與 Thread 實現瞭解耦。Thread 負責線程的啓動以及相關屬性設置。
在一些狀況下能夠提升性能。好比:線程執行的內容很簡單,就是打印個日誌。若是使用 Thread 實現,那它會從線程建立到銷燬都要走一遍,須要屢次執行時,還須要屢次走這重複的流程,內存開銷很是大。
可是咱們使用 Runnable 就不同了。能夠把它扔到線程池裏面,用固定的線程執行。這樣,顯然是能夠提升效率的。
若是看到這裏,喜歡這篇文章的話,請幫點個好看。微信搜索一個優秀的廢人,關注後回覆電子書送你 100+ 本編程電子書 ,不僅 Java 哦,詳情看下圖。回覆1024送你一套完整的 java 視頻教程。