當阿里面試官問我:Java建立執行緒有幾種方式?我就知道問題沒那麼簡單
這是最新的大廠面試系列,還原真實場景,提煉出知識點分享給大家。
點贊再看,養成習慣~ 微信搜尋【武哥聊程式設計】,關注這個 Java 菜鳥。
昨天有個小夥伴去阿里面試實習生崗位,面試官問他了一個老生常談的問題:你說一說 Java 建立執行緒都有哪些方式?
這哥們心中竊喜,這個老生常談的問題早已背的滾瓜爛熟,於是很流利的說了出來。
Java 建立執行緒有兩種方式:
- 繼承
Thread
類,並重寫run()
方法- 實現
Runnable
介面,覆蓋介面中的run()
方法,並把Runnable
介面的實現扔給Thread
面試官:(拿出一張白紙)那你把這兩種方式寫一下吧!
小哥:(這有何難!)好~
public static void main(String[] args) { // 第一種 MyThread myThread = new MyThread(); myThread.start(); // 第二種 new Thread(() -> System.out.println("自己實現的run-2")).start(); } public static class MyThread extends Thread { @Override public void run() { System.out.println("自己實現的run-1"); } }
面試官:嗯,那除了這兩種,還有其他建立執行緒的方法嗎?
這些簡單的問題難不倒這哥們,於是他想到了 Java5 之後的Executors
,Executors
工具類可以用來建立執行緒池。
小哥:Executors
工具類是用來建立執行緒池的,這個執行緒池可以指定執行緒個數,也可以不指定,也可以指定定時器的執行緒池,它有如下常用的方法:
newFixedThreadPool(int nThreads)
:建立固定數量的執行緒池
newCachedThreadPool()
:建立快取執行緒池
newSingleThreadExecutor()
:建立單個執行緒
newScheduledThreadPool(int corePoolSize)
:建立定時器執行緒池
面試官:嗯,OK,咱們還是針對你剛剛寫的程式碼,我再問你個問題。
此時這哥們有種不祥的預感,是不是自己程式碼寫的有點問題?或者要問我底層實現?
面試官:你寫的兩種建立執行緒的方式,都涉及到了run()
方法,你瞭解過Thread
裡的run()
方法具體是怎麼實現的嗎?
果然問到了原始碼了,這哥們之前看了點,所以不是很慌,回憶了一下,向面試官道來。
小哥:emm……Thread
中的run()
方法裡東西很少,就一個 if 判斷:
@Override public void run() { if (target != null) { target.run(); } }
有個target
物件,會去判斷該變數是否為空,非空的時候,去執行target
物件中的run()
方法,否則啥也不幹。而這個target
物件,就是我們說的Runnable
:
/* What will be run. */
private Runnable target;
面試官:嗯,那這個Runnable
類你瞭解過嗎?
小哥:瞭解過,這個Runnable
類很簡單,就一個抽象方法:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
這個抽象方法也是run()
!如果我們使用Runnable
介面,就需要實現這個run()
方法。由於這個Runnable
類上面標了@FunctionalInterface
註解,所以可以使用函數語言程式設計。
那小哥接著說:(突然自信起來了)所以這就對應了剛才說的兩種建立執行緒的方式,假如我用第一種方式:繼承了Thread
類,然後重寫了run()
方法,那麼它就不會去執行上面這個預設的run()
方法了(即不會去判斷target
),會執行我重寫的run()
方法邏輯。
假如我是用的第二種方式:實現Runnable
介面的方式,那麼它會執行預設的run()
方法,然後判斷target
不為空,再去執行我在Runnable
介面中實現的run()
方法。
面試官:OK,可以,我再問你個問題。
小哥:(暗自竊喜)
面試官:那如果我既繼承了Thread
類,同時我又實現了Runnable
介面,比如這樣,最後會列印什麼資訊出來呢?
面試官邊說邊拿起剛剛這小哥寫的程式碼,對它進行了簡單的修改:
public static void main(String[] args) {
new Thread(() -> System.out.println("runnable run")) {
@Override
public void run() {
System.out.println("Thread run");
}
}.start();
}
這小哥,突然有點懵,好像從來沒想過這個問題,一時沒有什麼思路,於是回答了個:會列印 “Thread run” 吧……
面試官:答案是對的,但是為什麼呢?
這小哥一時沒想到原因,於是面試官讓他回去可以思考思考,就繼續下一個問題了。
親愛的讀者朋友,你們知道為什麼嗎?你們可以先思考一下。
其實這個答案很簡單,我們來分析一下程式碼便知:其實是 new 了一個物件(子物件)繼承了Thread
物件(父物件),在子物件裡重寫了父類的run()
方法;然後父物件裡面扔了個Runnable
進去,父物件中的run()
方法就是最初那個帶有 if 判斷的run()
方法。
好了,現在執行start()
後,肯定先在子類中找run()
方法,找到了,父類的run()
方法自然就被幹掉了,所以會打印出:Thread run。
如果我們現在假設子類中沒有重寫run()
方法,那麼必然要去父類找run()
方法,父類的run()
方法中就得判斷是否有Runnable
傳進來,現在有一個,所以執行Runnable
中的run()
方法,那麼就會列印:Runnable run 出來。
說白了,就是問了個 Java 語言本身的父子繼承關係,會優先執行子類重寫的方法而已,只是借這個場景,換了個提問的方式,面試者可能一時沒反應過來,有點懵也是正常的,如果直接問,傻子都能回答的出來。
後記:通過這道簡單的面試題,幫大家分析了一下在建立執行緒過程中的原始碼,可以看出來,面試過程中,面試官更加看重一些原理性的東西,而不是背一下方式就行了。同時也能看的出,面試大廠,需要做好充分的準備。另外,在面試的時候要冷靜,可能有些問題並沒有太難,回答不出來只是當時太緊張造成的。
這篇文章就寫到這,最後祝大家都能面試成功。
如果覺得有幫助,希望老鐵們來個三連擊,給更多的人看到這篇文章
1、關注我的原創微信公眾號「武哥聊程式設計」,專注於Java、資料結構和演算法、微服務、中介軟體等技術分享,保證你看完有所收穫。
2、給俺點個讚唄,可以讓更多的人看到這篇文章,順便激勵下我繼續寫作,嘻嘻。