1. 程式人生 > >當阿里面試官問我:Java建立執行緒有幾種方式?我就知道問題沒那麼簡單

當阿里面試官問我:Java建立執行緒有幾種方式?我就知道問題沒那麼簡單

這是最新的大廠面試系列,還原真實場景,提煉出知識點分享給大家。
點贊再看,養成習慣~ 微信搜尋【武哥聊程式設計】,關注這個 Java 菜鳥。

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

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

Java 建立執行緒有兩種方式:

  1. 繼承Thread類,並重寫run()方法
  2. 實現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 之後的ExecutorsExecutors工具類可以用來建立執行緒池。

小哥: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、給俺點個讚唄,可以讓更多的人看到這篇文章,順便激勵下我繼續寫作,嘻嘻。