最近再看多線程這方面的知識,所以對於線程池這個知識點是必須看的。所以用來做一個簡單的記錄。話不多說,先上代碼來。這裏的代碼都是簡單的實現,並沒有什麼特別的地方。當然,你也可以copy過去在自己的電腦上執行。
先寫一個實現 Runnable的類,用來執行
package com.yang.threadspringcase.config;
/*
*@author:BaoYang
*@Date: 2020/3/2
*@description:
*/
public class GavinTask implements Runnable
{
@Override
public void run()
{
System.out.println("線程號 ---> " + Thread.currentThread().getName() + " GavinTask Running is OK.");
}
}
CachedThreadPool 線程池
package com.yang.threadspringcase.config.threadpool;
/*
*@author:BaoYang
*@Date: 2020/3/2
*@description:
*/
import com.yang.threadspringcase.config.GavinTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPoolExecutor
{
public static void main(String[] args)
{
ExecutorService executorService = Executors.newCachedThreadPool();
for(int i=0;i<10;i++){
executorService.execute(new GavinTask());
}
executorService.shutdown();
}
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
這裏我們可以看到在 線程池 pool-1中用了十條線程去執行,這個就可以看出來,只要小於Integer.MAX_VALUE的任務,就會創建相應的線程數去執行。 這個你可以將十修改爲20,30看結果。
fixedThreadPool線程池
package com.yang.threadspringcase.config.threadpool;
/*
*@author:BaoYang
*@Date: 2020/3/2
*@description:
*/
import com.yang.threadspringcase.config.GavinTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPoolExecutor
{
public static void main(String[] args)
{
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
for(int i=0;i<100;i++) {
fixedThreadPool.execute(new GavinTask());
}
fixedThreadPool.shutdown();
}
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
可以看出來,線程池中的線程數編號是不會的大於五的,這是因爲在創建的時候,核心線程數 等於 最大線程線程數,然後使用一個LinkedBlockingQueue來進行存儲多餘的任務.
ScheduledThreadPool 線程池
package com.yang.threadspringcase.config.threadpool;
/*
*@author:BaoYang
*@Date: 2020/3/2
*@description:
*/
import com.yang.threadspringcase.config.GavinTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPoolExecutor
{
public static void main(String[] args)
{
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
// scheduledThreadPool.schedule(new GavinTask(),3, TimeUnit.SECONDS);
for(int i=1;i<8;i++){
scheduledThreadPool.schedule(new GavinTask(),i, TimeUnit.SECONDS);
}
scheduledThreadPool.shutdown();
}
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
從執行的結果中可以看出來, 因爲沒有模擬耗時的業務來進行等待休眠等操作,所以可以看清晰的看出來,都是依次從核心線程數中的獲取執行的。這個主要是一個定時線程池。
SingleThreadPool 線程池
package com.yang.threadspringcase.config.threadpool;
/*
*@author:BaoYang
*@Date: 2020/3/2
*@description:
*/
import com.yang.threadspringcase.config.GavinTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExecutor
{
public static void main(String[] args)
{
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for(int i=0;i<10;i++){
singleThreadExecutor.execute(new GavinTask());
}
singleThreadExecutor.shutdown();
}
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
從結果中就可以看出來,這個線程池裏面永遠就只有一個線程去執行。然後從其實現調用的方法就可以看出來,核心線程數和最大線程數都是1,多餘的任務使用LinkedBlockingQueue來進行存儲,然後後面一次從這個集合中獲取出任務進行執行.
自定義線程池
package com.yang.threadspringcase.config.threadpool;
/*
*@author:BaoYang
*@Date: 2020/3/2
*@description:
*/
import com.yang.threadspringcase.config.GavinTask;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ExceptionThreadPool
{
public static void main(String[] args)
{
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
1, 2, 0,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(1));
for(int i=0;i<6;i++){
threadPoolExecutor.execute(new GavinTask());
}
}
}
這是因爲需要執行的任務已經大於你自定的線程數導致的。
這個圖就可以很好的證明上面的代碼邏輯。然後你將6換成2,就不會有這個錯誤了。
然後這裏記錄一下這些參數的使用和基本的意義.
- corePoolSize : 核心線程數,當任務來了,會先看核心線程數是否還有,如果有的話,直接創建
- maximumPoolSize: 最大線程數, 當執行的任務比最大線程數還多的話,就會看到上面的錯誤。
- keepAliveTime: 保持活躍時間,線程在 keepAliveTime設置的時間內沒有執行任務,就會被線程池回收。
- unit : 對應3中的事件類行,是分鐘還是秒等
- workQueue: 隊列的情況(看你使用的那種隊列)
我們可以點擊進去看看源碼分析流程
ThreadPoolExecutor ---> execute(Runnable command) 這個方法。可以使用這個翻譯來看下具體的信息
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
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);
}
- 先判斷傳入進來的 Runnable 是否爲 Null,如果是Null的話,就會拋出一個空指針異常
- 判斷corePoolSize是否還有,有的話 就會調用addWorker()方法,傳入 Runnable 和 true二個參數;如果成功就return,沒有成功就會走ctl.get() 賦值給c 然後程序繼續往下走
2.1 : 繼續往下走的話;判斷是否Running 和 隊列中是否可以添加進去;如果可以的話,ctl.get() 獲取 recheck的值。
2.1.1 判斷recheck的值是否running和並且remove掉runnable是成功的話,就會走 策略處理. 一般拋出異常。
2.1.2 判斷工作個數是否等於 0 ,是的會走addWork(null,false) 傳入進入
2.2 addWord(runnable,false) 來判斷是否啓動線程成功,如果不成功的話,就會走 策略處理。
這裏缺少核心的 addWord方法的說明,後續補上。