掌握線程池是後端程序員的基本要求,相信你們求職面試過程當中,幾乎都會被問到有關於線程池的問題。我在網上搜集了幾道經典的線程池面試題,並以此爲切入點,談談我對線程池的理解。若是有哪裏理解不正確,很是但願你們指出,接下來你們一塊兒分析學習吧。java
線程池: 簡單理解,它就是一個管理線程的池子。程序員
線程池能夠經過ThreadPoolExecutor來建立,咱們來看一下它的構造函數:面試
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 複製代碼
幾個核心參數的做用:後端
好的,到這裏。面試問題1->Java的線程池說一下,各個參數的做用,如何進行的? 是否已經迎刃而解啦, 我以爲這個問題,回答:線程池構造函數的corePoolSize,maximumPoolSize等參數,而且能描述清楚線程池的執行流程 就差很少啦。數組
在使用線程池處理任務的時候,任務代碼可能拋出RuntimeException,拋出異常後,線程池可能捕獲它,也可能建立一個新的線程來代替異常的線程,咱們可能沒法感知任務出現了異常,所以咱們須要考慮線程池異常狀況。緩存
咱們先來看一段代碼:併發
ExecutorService threadPool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
threadPool.submit(() -> {
System.out.println("current thread name" + Thread.currentThread().getName());
Object object = null;
System.out.print("result## "+object.toString());
});
}
複製代碼
顯然,這段代碼會有異常,咱們再來看看執行結果函數
雖然沒有結果輸出,可是沒有拋出異常,因此咱們沒法感知任務出現了異常,因此須要添加try/catch。 以下圖: 學習
OK,線程的異常處理, 咱們能夠直接try...catch捕獲。經過debug上面有異常的submit方法(建議你們也去debug看一下,圖上的每一個方法內部是我打斷點的地方),處理有異常submit方法的主要執行流程圖:this
//構造feature對象
/** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
//線程池執行
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();
}
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);
}
else if (!addWorker(command, false))
reject(command);
}
//捕獲異常
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
複製代碼
經過以上分析,submit執行的任務,能夠經過Future對象的get方法接收拋出的異常,再進行處理。 咱們再經過一個demo,看一下Future對象的get方法處理異常的姿式,以下圖:
除了以上1.在任務代碼try/catch捕獲異常,2.經過Future對象的get方法接收拋出的異常,再處理兩種方案外,還有以上兩種方案:
咱們直接看這樣實現的正確姿式:
ExecutorService threadPool = Executors.newFixedThreadPool(1, r -> {
Thread t = new Thread(r);
t.setUncaughtExceptionHandler(
(t1, e) -> {
System.out.println(t1.getName() + "線程拋出的異常"+e);
});
return t;
});
threadPool.execute(()->{
Object object = null;
System.out.print("result## " + object.toString());
});
複製代碼
運行結果:
這是jdk文檔的一個demo:
class ExtendedExecutor extends ThreadPoolExecutor {
// 這但是jdk文檔裏面給的例子。。
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (t == null && r instanceof Future<?>) {
try {
Object result = ((Future<?>) r).get();
} catch (CancellationException ce) {
t = ce;
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt(); // ignore/reset
}
}
if (t != null)
System.out.println(t);
}
}}
複製代碼
線程池都有哪幾種工做隊列?
ArrayBlockingQueue(有界隊列)是一個用數組實現的有界阻塞隊列,按FIFO排序量。
LinkedBlockingQueue(可設置容量隊列)基於鏈表結構的阻塞隊列,按FIFO排序任務,容量能夠選擇進行設置,不設置的話,將是一個無邊界的阻塞隊列,最大長度爲Integer.MAX_VALUE,吞吐量一般要高於ArrayBlockingQuene;newFixedThreadPool線程池使用了這個隊列
DelayQueue(延遲隊列)是一個任務定時週期的延遲執行的隊列。根據指定的執行時間從小到大排序,不然根據插入到隊列的前後排序。newScheduledThreadPool線程池使用了這個隊列。
PriorityBlockingQueue(優先級隊列)是具備優先級的無界阻塞隊列;
SynchronousQueue(同步隊列)一個不存儲元素的阻塞隊列,每一個插入操做必須等到另外一個線程調用移除操做,不然插入操做一直處於阻塞狀態,吞吐量一般要高於LinkedBlockingQuene,newCachedThreadPool線程池使用了這個隊列。
針對面試題:線程池都有哪幾種工做隊列? 我以爲,回答以上幾種ArrayBlockingQueue,LinkedBlockingQueue,SynchronousQueue等,說出它們的特色,並結合使用到對應隊列的經常使用線程池(如newFixedThreadPool線程池使用LinkedBlockingQueue),進行展開闡述, 就能夠啦。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
複製代碼
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < Integer.MAX_VALUE; i++) {
executor.execute(()->{
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
//do nothing
}
});
複製代碼
IDE指定JVM參數:-Xmx8m -Xms8m :
run以上代碼,會拋出OOM:
所以,面試題:使用無界隊列的線程池會致使內存飆升嗎?
答案 :會的,newFixedThreadPool使用了無界的阻塞隊列LinkedBlockingQueue,若是線程獲取一個任務後,任務的執行時間比較長(好比,上面demo設置了10秒),會致使隊列的任務越積越多,致使機器內存使用不停飆升, 最終致使OOM。
FixedThreadPool 適用於處理CPU密集型的任務,確保CPU在長期被工做線程使用的狀況下,儘量的少的分配線程,即適用執行長期的任務。
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
複製代碼
當提交任務的速度大於處理任務的速度時,每次提交一個任務,就必然會建立一個線程。極端狀況下會建立過多的線程,耗盡 CPU 和內存資源。因爲空閒 60 秒的線程會被終止,長時間保持空閒的 CachedThreadPool 不會佔用任何資源。
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
executor.execute(() -> {
System.out.println(Thread.currentThread().getName()+"正在執行");
});
}
複製代碼
運行結果:
用於併發執行大量短時間的小任務。
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
複製代碼
ExecutorService executor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
executor.execute(() -> {
System.out.println(Thread.currentThread().getName()+"正在執行");
});
}
複製代碼
運行結果:
適用於串行執行任務的場景,一個任務一個任務地執行。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
複製代碼
/** 建立一個給定初始延遲的間隔性的任務,以後的下次執行時間是上一次任務從執行到結束所須要的時間+* 給定的間隔時間 */
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
scheduledExecutorService.scheduleWithFixedDelay(()->{
System.out.println("current Time" + System.currentTimeMillis());
System.out.println(Thread.currentThread().getName()+"正在執行");
}, 1, 3, TimeUnit.SECONDS);
複製代碼
運行結果:
/** 建立一個給定初始延遲的間隔性的任務,以後的每次任務執行時間爲 初始延遲 + N * delay(間隔) */
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
scheduledExecutorService.scheduleAtFixedRate(()->{
System.out.println("current Time" + System.currentTimeMillis());
System.out.println(Thread.currentThread().getName()+"正在執行");
}, 1, 3, TimeUnit.SECONDS);;
複製代碼
週期性執行任務的場景,須要限制線程數量的場景
回到面試題:說說幾種常見的線程池及使用場景?
回答這四種經典線程池 :newFixedThreadPool,newSingleThreadExecutor,newCachedThreadPool,newScheduledThreadPool,分線程池特色,工做機制,使用場景分開描述,再分析可能存在的問題,好比newFixedThreadPool內存飆升問題 便可
線程池有這幾個狀態:RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED。
//線程池狀態
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
複製代碼
RUNNING
SHUTDOWN
STOP
TIDYING
TERMINATED
歡迎你們關注,你們一塊兒學習,一塊兒討論。