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

當阿里面試官問我: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 語言本身的父子繼承關係,會優先執行子類重寫的方法而已,只是借這個場景,換了個提問的方式,面試者可能一時沒反應過來,有點懵也是正常的,如果直接問,傻子都能回答的出來。

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

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

原文地址https://www.cnblogs.com/eson15/p/12666341.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章