本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,2萬元獎池等你挑戰!前端
這是我在掘金的第五篇文章了。感謝閱讀,並但願之後持續關注,我會輸出更多技術乾貨,咱們共同進步!java
之後可能會分爲幾大專題,相似於併發專題,源碼專題,面試專題等(只會分享乾貨)。面試
感興趣的能夠點擊我頭像查看歷史文章,每一篇都是乾貨哦!編程
建立線程到底有幾種方式,你都知道哪幾種?後端
咱們都知道實現線程是併發編程中基礎中的基礎,由於咱們必需要先實現多線程,才能夠繼續後續的一系列操做。因此咱們今天就先從併發編程的基礎如何實現線程開始講起,雖然實現線程看似簡單、基礎,但實際上卻暗藏玄機。markdown
實現線程的方式到底有幾種?大部分人會說有 2 種、3 種或是 4 種,不多有人會說有 1 種。那你們可能隨口說出來的就是兩種,先說你們熟悉的兩種方式!多線程
public class RunnableThread implements Runnable {
@Override
public void run() {
System.out.println('用實現Runnable接口實現線程');
}
}
複製代碼
第 1 種方式是經過實現 Runnable 接口實現多線程,如代碼所示,首先經過 RunnableThread 類實現 Runnable 接口,而後重寫 run() 方法,以後只須要把這個實現了 run() 方法的實例傳到 Thread 類中就能夠實現多線程。架構
public class ExtendsThread extends Thread {
@Override
public void run() {
System.out.println('用Thread類實現線程');
}
}
複製代碼
第 2 種方式是繼承 Thread 類,如代碼所示,與第 1 種方式不一樣的是它沒有實現接口,而是繼承 Thread 類,並重寫了其中的 run() 方法。相信上面這兩種方式你必定很是熟悉,而且常常在工做中使用它們。併發
那麼爲何說還有第 3 種或第 4 種方式呢?咱們先來看看第 3 種方式:經過線程池建立線程。線程池確實實現了多線程,好比咱們給線程池的線程數量設置成 10,那麼就會有 10 個子線程來爲咱們工做,接下來,咱們深刻解析線程池中的源碼,來看看線程池是怎麼實現線程的?dom
static class DefaultThreadFactory implements ThreadFactory {
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
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() 建立線程的 ,只不過這裏的構造函數傳入的參數要多一些,由此能夠看出經過線程池建立線程並無脫離最開始的那兩種基本的建立方式,由於本質上仍是經過 new Thread() 實現的。
class CallableTask 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 CallableTask());
複製代碼
第 4 種線程建立方式是經過有返回值的 Callable 建立線程,Runnable 建立線程是無返回值的,而 Callable 和與之相關的 Future、FutureTask,它們能夠把線程執行的結果做爲返回值返回,如代碼所示,實現了 Callable 接口,而且給它的泛型設置成 Integer,而後它會返回一個隨機數。
可是,不管是 Callable 仍是 FutureTask,它們首先和 Runnable 同樣,都是一個任務,是須要被執行的,而不是說它們自己就是線程。它們能夠放到線程池中執行,如代碼所示, submit() 方法把任務放到線程池中,並由線程池建立線程,無論用什麼方法,最終都是靠線程來執行的,而子線程的建立方式仍脫離不了最開始講的兩種基本方式,也就是實現 Runnable 接口和繼承 Thread 類。 是的 沒錯,本質仍是Runnable和Thread
關於這個問題,咱們先不聚焦爲何說建立線程只有一種方式,先認爲有兩種建立線程的方式,而其餘的建立方式,好比線程池或是定時器,它們僅僅是在 new Thread() 外作了一層封裝。
若是咱們把這些都叫做一種新的方式,那麼建立線程的方式便會變幻無窮、層出不窮,好比 JDK 更新了,它可能會多出幾個類,會把 new Thread() 從新封裝,表面上看又會是一種新的實現線程的方式,透過現象看本質,打開封裝後,會發現它們最終都是基於 Runnable 接口或繼承 Thread 類實現的!
@Override
public void run() {
if (target != null) {
target.run();
}
}
複製代碼
首先,啓動線程須要調用 start() 方法,而 start() 方法最終還會調用 run() 方法,咱們先來看看第一種方式中 run() 方法到底是怎麼實現的,能夠看出 run() 方法的代碼很是短小精悍,第 1 行代碼 if (target != null) ,判斷 target 是否等於 null,若是不等於 null,就執行第 2 行代碼 target.run(),而 target 實際上就是一個 Runnable,即便用 Runnable 接口實現線程時傳給Thread類的對象。
而後,咱們來看第二種方式,也就是繼承 Thread 方式,實際上,繼承 Thread 類以後,會把上述的 run() 方法重寫,重寫後 run() 方法裏直接就是所須要執行的任務,但它最終仍是須要調用 thread.start() 方法來啓動線程,而 start() 方法最終也會調用這個已經被重寫的 run() 方法來執行它的任務.
這時咱們就能夠完全明白了,事實上建立線程只有一種方式,就是構造一個 Thread 類,這是建立線程的惟一方式。
本質上,實現線程只有一種方式,而要想實現線程執行的內容,卻有兩種方式,也就是能夠經過 實現 Runnable 接口的方式,或是繼承 Thread 類重寫 run() 方法的方式,把咱們想要執行的代碼傳入,讓線程去執行,在此基礎上,若是咱們還想有更多實現線程的方式,好比線程池和 Timer 定時器,只須要在此基礎上進行封裝便可。
拋出問題,咱們會想雖然只有一種建立線程的方法,那實現 Runnable 接口比繼承 Thread 類實現線程哪一個更好一些。好在哪裏?,我麼從如下幾個方法分析下
首先,咱們從代碼的架構考慮,實際上,Runnable 裏只有一個 run() 方法,它定義了須要執行的內容,在這種狀況下,實現了 Runnable 與 Thread 類的解耦,Thread 類負責線程啓動和屬性設置等內容,權責分明。
第二點就是在某些狀況下能夠提升性能,使用繼承 Thread 類方式,每次執行一次任務,都須要新建一個獨立的線程,執行完任務後線程走到生命週期的盡頭被銷燬,若是還想執行這個任務,就必須再新建一個繼承了 Thread 類的類,若是此時執行的內容比較少,好比只是在 run() 方法裏簡單打印一行文字,那麼它所帶來的開銷並不大,相比於整個線程從開始建立到執行完畢被銷燬,這一系列的操做比 run() 方法打印文字自己帶來的開銷要大得多,至關於撿了芝麻丟了西瓜,得不償失。若是咱們使用實現 Runnable 接口的方式,就能夠把任務直接傳入線程池,使用一些固定的線程來完成任務,不須要每次新建銷燬線程,大大下降了性能開銷。
第三點好處在於 Java 語言不支持雙繼承,若是咱們的類一旦繼承了 Thread 類,那麼它後續就沒有辦法再繼承其餘的類,這樣一來,若是將來這個類須要繼承其餘類實現一些功能上的拓展,它就沒有辦法作到了,至關於限制了代碼將來的可拓展性。
綜上所述,咱們應該優先選擇經過實現 Runnable 接口的方式來建立線程。
學習一點點,努力一點點,分享一點點,你們加油!