當阿里面試官問我:Java創建執行緒有幾種方式?我就知道問題沒那麼簡單
- 2020 年 4 月 9 日
- 筆記
這是最新的大廠面試系列,還原真實場景,提煉出知識點分享給大家。
點贊再看,養成習慣~ 微信搜索【武哥聊編程】,關注這個 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、給俺點個贊唄,可以讓更多的人看到這篇文章,順便激勵下我繼續寫作,嘻嘻。