只有光頭才能變強java
回顧前面:算法
本篇主要是講解線程池,這是我在多線程的倒數第二篇了,後面還會有一篇死鎖。主要將多線程的基礎過一遍,之後有機會再繼續深刻!編程
那麼接下來就開始吧,若是文章有錯誤的地方請你們多多包涵,不吝在評論區指正哦~c#
聲明:本文使用JDK1.8api
線程池能夠看作是線程的集合。在沒有任務時線程處於空閒狀態,當請求到來:線程池給這個請求分配一個空閒的線程,任務完成後回到線程池中等待下次任務**(而不是銷燬)。這樣就實現了線程的重用**。微信
咱們來看看若是沒有使用線程池的狀況是這樣的:多線程
public class ThreadPerTaskWebServer {
public static void main(String[] args) throws IOException {
ServerSocket socket = new ServerSocket(80);
while (true) {
// 爲每一個請求都建立一個新的線程
final Socket connection = socket.accept();
Runnable task = () -> handleRequest(connection);
new Thread(task).start();
}
}
private static void handleRequest(Socket connection) {
// request-handling logic here
}
}
複製代碼
爲每一個請求都開一個新的線程雖然理論上是能夠的,可是會有缺點:架構
因此說:咱們的線程最好是交由線程池來管理,這樣能夠減小對線程生命週期的管理,必定程度上提升性能。併發
JDK給咱們提供了Excutor框架來使用線程池,它是線程池的基礎。框架
下面咱們來看看JDK線程池的整體api架構:
接下來咱們把這些API都過一遍看看:
Executor接口:
ExcutorService接口:
AbstractExecutorService類:
ScheduledExecutorService接口:
ThreadPoolExecutor類:
ScheduledThreadPoolExecutor類:
除了ScheduledThreadPoolExecutor和ThreadPoolExecutor類線程池之外,還有一個是JDK1.7新增的線程池:ForkJoinPool線程池
因而咱們的類圖就能夠變得完整一些:
JDK1.7中新增的一個線程池,與ThreadPoolExecutor同樣,一樣繼承了AbstractExecutorService。ForkJoinPool是Fork/Join框架的兩大核心類之一。與其它類型的ExecutorService相比,其主要的不一樣在於採用了工做竊取算法(work-stealing):全部池中線程會嘗試找到並執行已被提交到池中的或由其餘線程建立的任務。這樣不多有線程會處於空閒狀態,很是高效。這使得可以有效地處理如下情景:大多數由任務產生大量子任務的狀況;從外部客戶端大量提交小任務到池中的狀況。
來源:
學到了線程池,咱們能夠很容易地發現:不少的API都有Callable和Future這麼兩個東西。
Future<?> submit(Runnable task)
<T> Future<T> submit(Callable<T> task) 複製代碼
其實它們也不是什麼高深的東西~~~
咱們能夠簡單認爲:Callable就是Runnable的擴展。
也就是說:當咱們的任務須要返回值的時,咱們就可使用Callable!
Future通常咱們認爲是Callable的返回值,但他其實表明的是任務的生命週期(固然了,它是能獲取獲得Callable的返回值的)
簡單來看一下他們的用法:
public class CallableDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 建立線程池對象
ExecutorService pool = Executors.newFixedThreadPool(2);
// 能夠執行Runnable對象或者Callable對象表明的線程
Future<Integer> f1 = pool.submit(new MyCallable(100));
Future<Integer> f2 = pool.submit(new MyCallable(200));
// V get()
Integer i1 = f1.get();
Integer i2 = f2.get();
System.out.println(i1);
System.out.println(i2);
// 結束
pool.shutdown();
}
}
複製代碼
Callable任務:
public class MyCallable implements Callable<Integer> {
private int number;
public MyCallable(int number) {
this.number = number;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int x = 1; x <= number; x++) {
sum += x;
}
return sum;
}
}
複製代碼
執行完任務以後能夠獲取獲得任務返回的數據:
這是用得最多的線程池,因此本文會重點講解它。
咱們來看看頂部註釋:
變量ctl定義爲AtomicInteger,記錄了「線程池中的任務數量」和「線程池的狀態」兩個信息。
線程的狀態:
各個狀態之間轉換:
下面我就列舉三個比較常見的實現池:
若是讀懂了上面對應的策略呀,線程數量這些,應該就不會太難看懂了。
一個固定線程數的線程池,它將返回一個corePoolSize和maximumPoolSize相等的線程池。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
複製代碼
很是有彈性的線程池,對於新的任務,若是此時線程池裏沒有空閒線程,線程池會堅決果斷的建立一條新的線程去處理這個任務。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
複製代碼
使用單個worker線程的Executor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
複製代碼
咱們讀完上面的默認實現池還有對應的屬性,再回到構造方法看看
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
複製代碼
再總結一遍這些參數的要點:
線程數量要點:
線程空閒時間要點:
排隊策略要點:
當線程關閉或者線程數量滿了和隊列飽和了,就有拒絕任務的狀況了:
拒絕任務策略:
execute執行方法分了三步,以註釋的方式寫在代碼上了~
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//若是線程池中運行的線程數量<corePoolSize,則建立新線程來處理請求,即便其餘輔助線程是空閒的。
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//若是線程池中運行的線程數量>=corePoolSize,且線程池處於RUNNING狀態,且把提交的任務成功放入阻塞隊列中,就再次檢查線程池的狀態,
// 1.若是線程池不是RUNNING狀態,且成功從阻塞隊列中刪除任務,則該任務由當前 RejectedExecutionHandler 處理。
// 2.不然若是線程池中運行的線程數量爲0,則經過addWorker(null, false)嘗試新建一個線程,新建線程對應的任務爲null。
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);
}
// 若是以上兩種case不成立,即沒能將任務成功放入阻塞隊列中,且addWoker新建線程失敗,則該任務由當前 RejectedExecutionHandler 處理。
else if (!addWorker(command, false))
reject(command);
}
複製代碼
ThreadPoolExecutor提供了shutdown()
和shutdownNow()
兩個方法來關閉線程池
shutdown() :
shutdownNow():
區別:
本篇博文主要簡單地將多線程的結構體系過了一篇,講了最經常使用的ThreadPoolExecutor線程池是怎麼使用的~~~
明天但願能夠把死鎖寫出來,敬請期待~~~
還有剩下的幾個線程池(給出了參考資料):
參考資料:
若是文章有錯的地方歡迎指正,你們互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同窗,能夠關注微信公衆號:Java3y。
文章的目錄導航: