最近半年,經常有人問我 「Android就業市場究竟怎麼樣,我還能不能堅持下去 ?」java
如今想一想,移動互聯網的發展不知不覺已經十多年了,Mobile First 也已經變成了 AI First。換句話說,咱們已經再也不是「風口上的豬」。移動開發的光環和溢價開始慢慢消失,而且正在向 AI、區塊鏈等新的領域轉移。移動開發的新鮮血液也已經變少,最明顯的是國內應屆生都紛紛涌向了 AI 方向。面試
能夠說,國內移動互聯網的紅利期已通過去了,如今是增量降低、存量廝殺,從爭奪用戶到爭奪時長。比較明顯的是手機廠商紛紛互聯網化,與傳統互聯網企業直接競爭。另一方面,過去渠道的打法失靈,小程序、快應用等新興渠道崛起,不管是手機廠商,仍是各大 App 都把出海擺到了戰略的位置。數據庫
各大培訓市場也再也不培訓Android,做爲開發Android的咱們該何去何從?小程序
其實若是你技術深度足夠,大必不用爲就業而憂愁。每一個行業未嘗不是這樣,最開始的風口,到慢慢的成熟。Android初級在2019年的日子裏風光再也不, 靠會四大組件就可以獲取到滿意薪資的時代一去不復返。**通過一波一波的淘汰與洗牌,剩下的都是技術的金子。就像大浪褪去,裸泳的會慢慢上岸。**而真正堅持下來的必定會取得不錯成績。畢竟Android市場是如此之大。從Android高級的蓬勃的就業崗位需求來看,能堅信咱們每一位Android開發者的夢想 。後端
接下來咱們針對Android高級展開的完整面試題 2019Android74道高級面試題合集目錄(含BAT 字節跳動等等)緩存
專一分享大型Bat面試知識,後續會持續更新,喜歡的話麻煩點擊關注一下bash
線程池能夠簡單看作是一組線程的集合,經過使用線程池,咱們能夠方便的複用線程,避免了頻繁建立和銷燬線程所帶來的開銷。在應用上,線程池可應用在後端相關服務中。好比 Web 服務器,數據庫服務器等。以 Web 服務器爲例,假如 Web 服務器會收到大量短時的 HTTP 請求,若是此時咱們簡單的爲每一個 HTTP 請求建立一個處理線程,那麼服務器的資源將會很快被耗盡。固然咱們也能夠本身去管理並複用已建立的線程,以限制資源的消耗量,但這樣會使用程序的邏輯變複雜。好在,幸運的是,咱們沒必要那樣作。在 JDK 1.5 中,官方已經提供了強大的線程池工具類。經過使用這些工具類,咱們能夠用低廉的代價使用多線程技術。服務器
線程池做爲 Java 併發重要的工具類,在會用的基礎上,我以爲頗有必要去學習一下線程池的相關原理。畢竟線程池除了要管理線程,還要管理任務,同時還要具有統計功能。因此多瞭解一點,仍是能夠擴充眼界的,同時也能夠更爲熟悉線程池技術。多線程
線程池所涉及到的接口和類並非不少,其繼承體系也相對簡單。相關繼承關係以下: 併發
如上圖,最頂層的接口 Executor
僅聲明瞭一個方法execute。ExecutorService 接口在其父類接口基礎上,聲明瞭包含但不限於shutdown
、submit
、invokeAll
、invokeAny
等方法。至於 ScheduledExecutorService 接口,則是聲明瞭一些和定時任務相關的方法,好比 schedule
和scheduleAtFixedRate
。線程池的核心實現是在 ThreadPoolExecutor 類中,咱們使用 Executors 調用newFixedThreadPool
、newSingleThreadExecutor
和newCachedThreadPool
等方法建立線程池均是 ThreadPoolExecutor 類型。
以上是對線程池繼承體系的簡單介紹,這裏先讓你們對線程池大體輪廓有必定的瞭解。接下來我會介紹一下線程池的實現原理,繼續往下看吧。
如上節所說,線程池的核心實現即 ThreadPoolExecutor 類。該類包含了幾個核心屬性,這些屬性在可在構造方法進行初始化。在介紹核心屬性前,咱們先來看看 ThreadPoolExecutor 的構造方法,以下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
複製代碼
如上所示,構造方法的參數即核心參數,這裏我用一個表格來簡要說明一下各個參數的意義。以下:
以上是各個參數的簡介,下面我將會針對部分參數進行詳細說明,繼續往下看。
在 Java 線程池實現中,線程池所能建立的線程數量受限於 corePoolSize 和 maximumPoolSize 兩個參數值。線程的建立時機則和 corePoolSize 以及 workQueue 兩個參數有關。下面列舉一下線程建立的4個規則(線程池中無空閒線程),以下:
簡化一下上面的規則:
考慮到系統資源是有限的,對於線程池超出 corePoolSize 數量的空閒線程應進行回收操做。進行此操做存在一個問題,即回收時機。目前的實現方式是當線程空閒時間超過 keepAliveTime 後,進行回收。除了核心線程數以外的線程能夠進行回收,核心線程內的空閒線程也能夠進行回收。回收的前提是allowCoreThreadTimeOut
屬性被設置爲 true,經過public void allowCoreThreadTimeOut(boolean)
方法能夠設置屬性值。
如3.1.2 線程建立規則一節中規則2所說,當線程數量大於等於 corePoolSize,workQueue 未滿時,則緩存新任務。這裏要考慮使用什麼類型的容器緩存新任務,經過 JDK 文檔介紹,咱們可知道有3中類型的容器可供使用,分別是同步隊列
,有界隊列
和無界隊列
。對於有優先級的任務,這裏還能夠增長優先級隊列
。以上所介紹的4中類型的隊列,對應的實現類以下:
如3.1.2 線程建立規則一節中規則4所說,線程數量大於等於 maximumPoolSize,且 workQueue 已滿,則使用拒絕策略處理新任務。Java 線程池提供了4中拒絕策略實現類,以下:
以上4個拒絕策略中,AbortPolicy 是線程池實現類所使用的策略。咱們也能夠經過方法public void setRejectedExecutionHandler(RejectedExecutionHandler)
修改線程池決絕策略。
在線程池的實現上,線程的建立是經過線程工廠接口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);
}
}
複製代碼
一般狀況下,咱們能夠經過線程池的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;
}
複製代碼
上面的代碼略多,不過結合上面的流程圖,和我所寫的註釋,理解主邏輯應該不難。
咱們能夠經過shutdown
和shutdownNow
兩個方法關閉線程池。兩個方法的區別在於,shutdown
會將線程池的狀態設置爲SHUTDOWN,同時該方法還會中斷空閒線程。shutdownNow 則會將線程池狀態設置爲STOP
,並嘗試中斷全部的線程。中斷線程使用的是Thread.interrupt
方法,未響應中斷方法的任務是沒法被中斷的。最後,shutdownNow 方法會將未執行的任務所有返回。
調用 shutdown 和 shutdownNow 方法關閉線程池後,就不能再向線程池提交新任務了。對於處於關閉狀態的線程池,會使用拒絕策略處理新提交的任務。
通常狀況下,咱們並不直接使用 ThreadPoolExecutor 類建立線程池,而是經過 Executors 工具類去構建線程池。經過 Executors 工具類,咱們能夠構造5中不一樣的線程池。下面經過一個表格簡單介紹一下幾種線程池,以下:
下一篇逐步講解2019Android高級面試題阿里篇第二道面試題:垃圾回收機制的實現