阿里面試官問:Java創建線程有幾種方式?我就知道問題沒那麼簡單


阿里面試官問:Java創建線程有幾種方式?我就知道問題沒那麼簡單


昨天有個小夥伴去阿里面試實習生崗位,面試官問他了一個老生常談的問題:你說一說 Java 創建線程都有哪些方式?

這哥們心中竊喜,這個老生常談的問題早已背的滾瓜爛熟,於是很流利的說了出來。

Java 創建線程有兩種方式:

繼承Thread類,並重寫run()方法實現Runnable接口,覆蓋接口中的run()方法,並把Runnable接口的實現扔給Thread

面試官:(拿出一張白紙)那你把這兩種方式寫一下吧!

小哥:(這有何難!)好~

<code>public static void main(String[] args) {
\t// 第一種
MyThread myThread = new MyThread();
myThread.start();
\t// 第二種
new Thread(() -> System.out.println("自己實現的run-2")).start();
}

public static class MyThread extends Thread {
@Override
public void run() {
System.out.println("自己實現的run-1");
}
}/<code>

面試官:嗯,那除了這兩種,還有其他創建線程的方法嗎?

這些簡單的問題難不倒這哥們,於是他想到了 Java5 之後的Executors,Executors工具類可以用來創建線程池。

小哥:Executors工具類是用來創建線程池的,這個線程池可以指定線程個數,也可以不指定,也可以指定定時器的線程池,它有如下常用的方法:

newFixedThreadPool(int nThreads):創建固定數量的線程池newCachedThreadPool():創建緩存線程池newSingleThreadExecutor():創建單個線程newScheduledThreadPool(int corePoolSize):創建定時器線程池

面試官:嗯,OK,咱們還是針對你剛剛寫的代碼,我再問你個問題。

此時這哥們有種不祥的預感,是不是自己代碼寫的有點問題?或者要問我底層實現?

面試官:你寫的兩種創建線程的方式,都涉及到了run()方法,你瞭解過Thread裡的run()方法具體是怎麼實現的嗎?

果然問到了源碼了,這哥們之前看了點,所以不是很慌,回憶了一下,向面試官道來。

小哥:emm……Thread 中的run()方法裡東西很少,就一個 if 判斷:

<code>@Override
public void run() {
\tif (target != null) {
\t\ttarget.run();

\t}
}/<code>

有個target對象,會去判斷該變量是否為空,非空的時候,去執行target對象中的run()方法,否則啥也不幹。而這個target對象,就是我們說的Runnable:

<code>/* What will be run. */
private Runnable target;/<code>

面試官:嗯,那這個Runnable類你瞭解過嗎?小哥:瞭解過,這個Runnable類很簡單,就一個抽象方法:

<code>@FunctionalInterface
public interface Runnable {
public abstract void run();
}/<code>

這個抽象方法也是run()!如果我們使用Runnable接口,就需要實現這個run()方法。由於這個Runnable類上面標了@FunctionalInterface註解,所以可以使用函數式編程。

那小哥接著說:(突然自信起來了)所以這就對應了剛才說的兩種創建線程的方式,假如我用第一種方式:繼承了Thread類,然後重寫了run()方法,那麼它就不會去執行上面這個默認的run()方法了(即不會去判斷target),會執行我重寫的run()方法邏輯。

假如我是用的第二種方式:實現Runnable接口的方式,那麼它會執行默認的run()方法,然後判斷target不為空,再去執行我在Runnable接口中實現的run()方法。

面試官:OK,可以,我再問你個問題。

小哥:(暗自竊喜)

面試官:那如果我既繼承了Thread類,同時我又實現了Runnable接口,比如這樣,最後會打印什麼信息出來呢?

面試官邊說邊拿起剛剛這小哥寫的代碼,對它進行了簡單的修改:

<code>public static void main(String[] args) {
new Thread(() -> System.out.println("runnable run")) {
@Override
public void run() {
System.out.println("Thread run");
}
}.start();
}/<code>

這小哥,突然有點懵,好像從來沒想過這個問題,一時沒有什麼思路,於是回答了個:會打印 “Thread run” 吧……

面試官:答案是對的,但是為什麼呢?

這小哥一時沒想到原因,於是面試官讓他回去可以思考思考,就繼續下一個問題了。

親愛的讀者朋友,你們知道為什麼嗎?你們可以先思考一下。

其實這個答案很簡單,我們來分析一下代碼便知:其實是 new 了一個對象(子對象)繼承了Thread對象(父對象),在子對象裡重寫了父類的run()方法;然後父對象裡面扔了個Runnable進去,父對象中的run()方法就是最初那個帶有 if 判斷的run()方法。

好了,現在執行start()後,肯定先在子類中找run()方法,找到了,父類的run()方法自然就被幹掉了,所以會打印出:Thread run。

如果我們現在假設子類中沒有重寫run()方法,那麼必然要去父類找run()方法,父類的run()方法中就得判斷是否有Runnable傳進來,現在有一個,所以執行Runnable中的run()方法,那麼就會打印:Runnable run 出來。

說白了,就是問了個 Java 語言本身的父子繼承關係,會優先執行子類重寫的方法而已,只是借這個場景,換了個提問的方式,面試者可能一時沒反應過來,有點懵也是正常的,如果直接問,傻子都能回答的出來。

後記:通過這道簡單的面試題,幫大家分析了一下在創建線程過程中的源碼,可以看出來,面試過程中,面試官更加看重一些原理性的東西,而不是背一下方式就行了。同時也能看的出,面試大廠,需要做好充分的準備。另外,在面試的時候要冷靜,可能有些問題並沒有太難,回答不出來只是當時太緊張造成的。

這篇文章就寫到這,最後祝大家都能面試成功。

如果覺得有幫助,希望老鐵們來個三連擊,給更多的人看到這篇文章


分享到:


相關文章: