2019Android74道高級面試合集(含BAT字節跳動等等)

前言

最近半年,經常有人問我 「Android就業市場究竟怎麼樣,我還能不能堅持下去 ?」java

如今想一想,移動互聯網的發展不知不覺已經十多年了,Mobile First 也已經變成了 AI First。換句話說,咱們已經再也不是「風口上的豬」。移動開發的光環和溢價開始慢慢消失,而且正在向 AI、區塊鏈等新的領域轉移。移動開發的新鮮血液也已經變少,最明顯的是國內應屆生都紛紛涌向了 AI 方向。面試

​ 能夠說,國內移動互聯網的紅利期已通過去了,如今是增量降低、存量廝殺,從爭奪用戶到爭奪時長。比較明顯的是手機廠商紛紛互聯網化,與傳統互聯網企業直接競爭。另一方面,過去渠道的打法失靈,小程序、快應用等新興渠道崛起,不管是手機廠商,仍是各大 App 都把出海擺到了戰略的位置。數據庫

各大培訓市場也再也不培訓Android,做爲開發Android的咱們該何去何從?小程序

​ 其實若是你技術深度足夠,大必不用爲就業而憂愁。每一個行業未嘗不是這樣,最開始的風口,到慢慢的成熟。Android初級在2019年的日子裏風光再也不, 靠會四大組件就可以獲取到滿意薪資的時代一去不復返。**通過一波一波的淘汰與洗牌,剩下的都是技術的金子。就像大浪褪去,裸泳的會慢慢上岸。**而真正堅持下來的必定會取得不錯成績。畢竟Android市場是如此之大。從Android高級的蓬勃的就業崗位需求來看,能堅信咱們每一位Android開發者的夢想 。後端

接下來咱們針對Android高級展開的完整面試題 2019Android74道高級面試題合集目錄(含BAT 字節跳動等等)緩存

阿里巴巴 談談你對Android線程池原理的理解

專一分享大型Bat面試知識,後續會持續更新,喜歡的話麻煩點擊關注一下bash

1.簡介

線程池能夠簡單看作是一組線程的集合,經過使用線程池,咱們能夠方便的複用線程,避免了頻繁建立和銷燬線程所帶來的開銷。在應用上,線程池可應用在後端相關服務中。好比 Web 服務器,數據庫服務器等。以 Web 服務器爲例,假如 Web 服務器會收到大量短時的 HTTP 請求,若是此時咱們簡單的爲每一個 HTTP 請求建立一個處理線程,那麼服務器的資源將會很快被耗盡。固然咱們也能夠本身去管理並複用已建立的線程,以限制資源的消耗量,但這樣會使用程序的邏輯變複雜。好在,幸運的是,咱們沒必要那樣作。在 JDK 1.5 中,官方已經提供了強大的線程池工具類。經過使用這些工具類,咱們能夠用低廉的代價使用多線程技術。服務器

線程池做爲 Java 併發重要的工具類,在會用的基礎上,我以爲頗有必要去學習一下線程池的相關原理。畢竟線程池除了要管理線程,還要管理任務,同時還要具有統計功能。因此多瞭解一點,仍是能夠擴充眼界的,同時也能夠更爲熟悉線程池技術。多線程

2.繼承體系

線程池所涉及到的接口和類並非不少,其繼承體系也相對簡單。相關繼承關係以下: 併發

如上圖,最頂層的接口 Executor 僅聲明瞭一個方法execute。ExecutorService 接口在其父類接口基礎上,聲明瞭包含但不限於shutdownsubmitinvokeAllinvokeAny 等方法。至於 ScheduledExecutorService 接口,則是聲明瞭一些和定時任務相關的方法,好比 schedulescheduleAtFixedRate。線程池的核心實現是在 ThreadPoolExecutor 類中,咱們使用 Executors 調用newFixedThreadPoolnewSingleThreadExecutornewCachedThreadPool等方法建立線程池均是 ThreadPoolExecutor 類型。

以上是對線程池繼承體系的簡單介紹,這裏先讓你們對線程池大體輪廓有必定的瞭解。接下來我會介紹一下線程池的實現原理,繼續往下看吧。

3.原理分析

3.1 核心參數分析

3.1.1 核心參數簡介

如上節所說,線程池的核心實現即 ThreadPoolExecutor 類。該類包含了幾個核心屬性,這些屬性在可在構造方法進行初始化。在介紹核心屬性前,咱們先來看看 ThreadPoolExecutor 的構造方法,以下:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
複製代碼

如上所示,構造方法的參數即核心參數,這裏我用一個表格來簡要說明一下各個參數的意義。以下:

以上是各個參數的簡介,下面我將會針對部分參數進行詳細說明,繼續往下看。

3.1.2 線程建立規則

在 Java 線程池實現中,線程池所能建立的線程數量受限於 corePoolSize 和 maximumPoolSize 兩個參數值。線程的建立時機則和 corePoolSize 以及 workQueue 兩個參數有關。下面列舉一下線程建立的4個規則(線程池中無空閒線程),以下:

  1. 線程數量小於 corePoolSize,直接建立新線程處理新的任務
  2. 線程數量大於等於 corePoolSize,workQueue 未滿,則緩存新任務
  3. 線程數量大於等於 corePoolSize,但小於 maximumPoolSize,且 workQueue 已滿。則建立新線程處理新任務
  4. 線程數量大於等於 maximumPoolSize,且 workQueue 已滿,則使用拒絕策略處理新任務

簡化一下上面的規則:

3.1.3 資源回收

考慮到系統資源是有限的,對於線程池超出 corePoolSize 數量的空閒線程應進行回收操做。進行此操做存在一個問題,即回收時機。目前的實現方式是當線程空閒時間超過 keepAliveTime 後,進行回收。除了核心線程數以外的線程能夠進行回收,核心線程內的空閒線程也能夠進行回收。回收的前提是allowCoreThreadTimeOut屬性被設置爲 true,經過public void allowCoreThreadTimeOut(boolean)方法能夠設置屬性值。

3.1.4 排隊策略

如3.1.2 線程建立規則一節中規則2所說,當線程數量大於等於 corePoolSize,workQueue 未滿時,則緩存新任務。這裏要考慮使用什麼類型的容器緩存新任務,經過 JDK 文檔介紹,咱們可知道有3中類型的容器可供使用,分別是同步隊列有界隊列無界隊列。對於有優先級的任務,這裏還能夠增長優先級隊列。以上所介紹的4中類型的隊列,對應的實現類以下:

3.1.5 拒絕策略

如3.1.2 線程建立規則一節中規則4所說,線程數量大於等於 maximumPoolSize,且 workQueue 已滿,則使用拒絕策略處理新任務。Java 線程池提供了4中拒絕策略實現類,以下:

以上4個拒絕策略中,AbortPolicy 是線程池實現類所使用的策略。咱們也能夠經過方法 public void setRejectedExecutionHandler(RejectedExecutionHandler)修改線程池決絕策略。

3.2 重要操做

3.2.1 線程的建立與複用

在線程池的實現上,線程的建立是經過線程工廠接口ThreadFactory的實現類來完成的。默認狀況下,線程池使用Executors.defaultThreadFactory()方法返回的線程工廠實現類。固然,咱們也能夠經過

public void setThreadFactory(ThreadFactory)方法進行動態修改。具體細節這裏就很少說了,並不複雜,你們能夠本身去看下源碼。

在線程池中,線程的複用是線程池的關鍵所在。這就要求線程在執行完一個任務後,不能當即退出。對應到具體實現上,工做線程在執行完一個任務後,會再次到任務隊列獲取新的任務。若是任務隊列中沒有任務,且 keepAliveTime 也未被設置,工做線程則會被一致阻塞下去。經過這種方式便可實現線程複用。

說完原理,再來看看線程的建立和複用的相關代碼(基於 JDK 1.8),以下:

+----ThreadPoolExecutor.Worker.java
Worker(Runnable firstTask) {
    setState(-1);
    this.firstTask = firstTask;
    // 調用線程工廠建立線程
    this.thread = getThreadFactory().newThread(this);
}

// Worker 實現了 Runnable 接口
public void run() {
    runWorker(this);
}

+----ThreadPoolExecutor.java
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock();
    boolean completedAbruptly = true;
    try {
        // 循環從任務隊列中獲取新任務
        while (task != null || (task = getTask()) != null) {
            w.lock();
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    // 執行新任務
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        // 線程退出後,進行後續處理
        processWorkerExit(w, completedAbruptly);
    }
}
複製代碼
3.2.2 提交任務

一般狀況下,咱們能夠經過線程池的submit方法提交任務。被提交的任務可能會當即執行,也可能會被緩存或者被拒絕。任務的處理流程以下圖所示:

上面的流程圖不是很複雜,下面再來看看流程圖對應的代碼,以下:

+---- AbstractExecutorService.java
public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    // 建立任務
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    // 提交任務
    execute(ftask);
    return ftask;
}

+---- ThreadPoolExecutor.java
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();

    int c = ctl.get();
    // 若是工做線程數量 < 核心線程數,則建立新線程
    if (workerCountOf(c) < corePoolSize) {
        // 添加工做者對象
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    
    // 緩存任務,若是隊列已滿,則 offer 方法返回 false。不然,offer 返回 true
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    
    // 添加工做者對象,並在 addWorker 方法中檢測線程數是否小於最大線程數
    else if (!addWorker(command, false))
        // 線程數 >= 最大線程數,使用拒絕策略處理任務
        reject(command);
}

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        for (;;) {
            int wc = workerCountOf(c);
            // 檢測工做線程數與核心線程數或最大線程數的關係
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        // 建立工做者對象,細節參考上一節所貼代碼
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                int rs = runStateOf(ctl.get());
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    // 將 worker 對象添加到 workers 集合中
                    workers.add(w);
                    int s = workers.size();
                    // 更新 largestPoolSize 屬性
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                // 開始執行任務
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}
複製代碼

上面的代碼略多,不過結合上面的流程圖,和我所寫的註釋,理解主邏輯應該不難。

3.2.3 關閉線程池

咱們能夠經過shutdownshutdownNow兩個方法關閉線程池。兩個方法的區別在於,shutdown 會將線程池的狀態設置爲SHUTDOWN,同時該方法還會中斷空閒線程。shutdownNow 則會將線程池狀態設置爲STOP,並嘗試中斷全部的線程。中斷線程使用的是Thread.interrupt方法,未響應中斷方法的任務是沒法被中斷的。最後,shutdownNow 方法會將未執行的任務所有返回。

調用 shutdown 和 shutdownNow 方法關閉線程池後,就不能再向線程池提交新任務了。對於處於關閉狀態的線程池,會使用拒絕策略處理新提交的任務。

4.幾種線程池

通常狀況下,咱們並不直接使用 ThreadPoolExecutor 類建立線程池,而是經過 Executors 工具類去構建線程池。經過 Executors 工具類,咱們能夠構造5中不一樣的線程池。下面經過一個表格簡單介紹一下幾種線程池,以下:

下一篇逐步講解2019Android高級面試題阿里篇第二道面試題:垃圾回收機制的實現

相關文章
相關標籤/搜索