程序員成長路上有着不同的階段,只要你翻過了當時那個階段,那麼你將會有了不一樣的收穫。很多時候,我們在剛開始面對它們的時候,還看不清,看不透,雲裏霧裏,讓人覺得它們很高深。等我們正在的瞭解它們了之後就覺一切都是那麼簡單、自然。
再努力一下,一切將會不一樣!—— 魯迅
前言
多線程開發就是這樣的一座山,需要我們去克服。說到多線程,大部分新手(作者自己),在面試中談到多線程就慌了,因爲自己在實際工作中真的很少碰到,而且我們多數時候都是在做傳統的單體項目開發,說真的,很少會碰到用到多線程的,用都沒用過面試的時候讓我們怎麼說。爲了讓大家對多線程有個大致瞭解,現在讓作者我跟大家瞎扯幾句,作者很少寫文章,寫得不太通順的地方,大家多(wang)多(si)諒(li)解(pen) 。
既然要講多線程,就不得不說下線程、線程池了~
線程
在Java中你會怎麼創建一個線程嗎?
很簡單啊,比如說:
Thread t = new Thread(new Runnable() {
@Override
public void run() {
// 處理邏輯代碼
process();
}
});
t.start();
恩,沒錯,這樣確實可以簡單的創建出一個線程對象。
我們知道一個線程的創建於銷燬是需要消耗資源的,大家來思考一個問題:假設我們項目中要經常用到多個線程去處理業務的話,每次都是用完就銷燬,這也未免太浪費了些吧,畢竟線程只是幫我們執行相應的任務,完全可以繼續複用它呀,讓它接着處理其他任務。那麼在Java中有沒有一種辦法使得線程可以複用,就是執行完一個任務,並不被銷燬,而是可以繼續執行其他的任務?
如果之前大家有看過《阿里巴巴 Java 手冊》的話,那麼應該知道有那麼一條:
可見線程池
就是我們的答案!
線程池的作用
簡單來說使用線程池有以下幾個目的:
-
線程是稀缺資源,不能頻繁的創建。
-
解耦作用,線程的創建於執行完全分開,方便維護。
-
應當將其放入一個池子中,可以給其他任務進行復用。
線程池原理
簡單來說就是把寶貴的資源管理起來,每次用的時候再去取,用完放回,讓其他人也可以複用。
那在Java中我們該怎麼實現呢?
在Java中線程池的核心類是ThreadPoolExecutor
,並在此類的基礎上封裝了幾種常用線程池:
-
Executors.newCachedThreadPool()
:無限線程池。 -
Executors.newFixedThreadPool(nThreads)
:創建固定大小的線程池。 -
Executors.newSingleThreadExecutor()
:創建單個線程的線程池。
我們看下他們是怎麼實現的:
// 無限線程池
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
// 創建固定大小的線程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
// 創建單個線程的線程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
看上面源代碼,我們知道它們都是基於ThreadPoolExecutor
實現的,那我們就來看看它們是給ThreadPoolExecutor
傳了什麼參數才導致它們可以實現不同功能的線程池的呢?
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
由上面的參數解釋,我們可以知道,線程池是通過設置corePoolSize
(最小線程數)和maximumPoolSize
(最大線程數)來確定線程池的大小範圍,讓線程池在我們定義範圍內擴容、減容。線程池的擴容是要判斷當前所需的線程數是否超過核心線程數
、阻塞隊列也滿了
並且當前線程數小於最大線程數
,都符合了,纔會擴容。我們可以看下面的流程圖來理解:
-
corePoolSize
核心線程數,爲線程池的基本大小。 -
maximumPoolSize
爲線程池最大線程大小。 -
keepAliveTime
和unit
則是線程空閒後的存活時間。 -
workQueue
用於存放任務的阻塞隊列。 -
threadFactory
線程工廠,主要用來創建線程。 -
handler
當隊列和最大線程池都滿了之後的飽和策略。-
ThreadPoolExecutor.AbortPolicy
丟棄任務並拋出RejectedExecutionException異常 -
ThreadPoolExecutor.DiscardPolicy
也是丟棄任務,但是不拋出異常 -
ThreadPoolExecutor.DiscardOldestPolicy
丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程) -
ThreadPoolExecutor.CallerRunsPolicy
由調用線程處理該任務
-
雖然,Executors
給我們封裝好了上面幾個構建線程池的方法,但是,並不建議直接使用!
爲什麼呢?
讓我們來看看阿里巴巴的開發手冊:
讓我們看下源碼是不是真的這樣:
-
Executors 源碼
// Executors.class
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));// 這裏採用了無參構造方法
}
-
LinkedBlockingQueue源碼
// LinkedBlockingQueue.class
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE); // 無參構造方法,直接設置爲Integer.MAX_VALUE大小
}
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
果然如此~
所以還是按照我們自己業務上需求自定義配置屬於自己的線程池吧!
好了,暫時先講到這啦~
大家覺得不錯幫忙點個在看哈~
參考:
https://blog.csdn.net/pange1991/article/details/53860651
《阿里巴巴Java開發手冊》