開篇
本文將從以下三個方面介紹線程池
- 線程池的七個參數
- 線程的使用
- 自定義一個線程池
- 一個任務提交會經歷哪些步驟
- JDK線程池是如何保證核心線程一直存活的
線程池的七個參數
java源碼
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize 核心線程數
*
* @param maximumPoolSize 最大線程數
*
* @param keepAliveTime 超出核心線程數的線程的存活時間
*
* @param unit 存活時間單位
*
* @param 工作隊列,也就是存放任務的阻塞隊列
*
* @param threadFactory 創建線程的工廠
*
* @param handler 當核心線程沒有空閒,祖舍隊列已滿,當前線程大於最大線程的拒絕策略
*
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
線程池的使用
線程池的使用非常簡單
普通線程池的使用
public class TestMyBlockQueue {
private static final int AVAILABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();
public static void main(String[] args) {
MyBlockQueue<String> stringMyBlockQueue = new MyBlockQueue<>(5);
// 直接創建一個線程池 推薦用法
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(AVAILABLE_PROCESSORS*4,AVAILABLE_PROCESSORS*4,
30,TimeUnit.MILLISECONDS,new ArrayBlockingQueue<>(100));
// 藉助Executors創建線程池 不推薦使用
ExecutorService threadPool = Executors.newFixedThreadPool(20);
for (int i = 0;i<10;i++) {
// 10個線程不put數據
threadPool.execute(()-> {
try {
while (true) {
stringMyBlockQueue.put("DSADSAD");
TimeUnit.SECONDS.sleep(10);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 10個線程不take數據
for (int i = 0;i<2;i++) {
threadPool.execute(()-> {
try {
while (true) {
stringMyBlockQueue.take();
TimeUnit.SECONDS.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
ScheduledThreadPool
除了普通線程池jdk還提供了一種帶定時的線程池,很多框架的延遲加載就是使用這種線程實現的,這個線程池就是ScheduledThreadPool,比如Dubbo的延遲加載,此外這個線程池也可以用來實現定時任務
public class TestScheduledThreadPool {
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);
// 提交一個任務1s後執行
scheduledThreadPool.schedule(new Runnable() {
public void run() {
System.out.println("你好1");
}
},1, TimeUnit.SECONDS);
// 創建並執行並結束一個runnable在延遲指定initialDelay時間,然後,每隔initialDelay+period*n時間執行一次
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println("你好2");
}
}, 1, 1, TimeUnit.SECONDS);
// 創建並執行並結束一個runnable在延遲指定initialDelay時間,然後第一次執行完成後,間隔delay時間繼續執行一次,無限循環。
scheduledThreadPool.scheduleWithFixedDelay(new Runnable() {
public void run() {
System.out.println("你好3");
}
},1000,100,TimeUnit.MILLISECONDS);
}
}
線程池的使用我們要切記不要定義方法中,大家可以思考一下爲什麼,這是一個相當危險的動作,此外線程池的關閉要注意哪些,比如在單例模式這個線程池是否可以關閉(關閉後會有什麼影響),不可的話如何讓線程池中的任務執行完成後再讓程序往下執行,有興趣的可以看一下下面的這個CountDownLatch的用法
public class CountDownLatchDemo {
private static CountDownLatch startSignal = new CountDownLatch(1);
//用來表示裁判員需要維護的是6個運動員
private static CountDownLatch endSignal = new CountDownLatch(6);
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(6);
for (int i = 0; i < 6; i++) {
executorService.execute(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 運動員等待裁判員響哨!!!");
// 所有的線程阻塞在這個地方
startSignal.await();
System.out.println(Thread.currentThread().getName() + "正在全力衝刺");
// 到達終點數量-1
endSignal.countDown();
System.out.println(Thread.currentThread().getName() + " 到達終點");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
TimeUnit.SECONDS.sleep(2);
System.out.println("裁判員發號施令啦!!!");
// 所有運動員準備完成
startSignal.countDown();
endSignal.await();
System.out.println("所有運動員到達終點,比賽結束!");
executorService.shutdown();
}
}
自定義線程池
這個自定義線程池基本就是參照jdk線程池來實現的,只是有些細節實現的比較粗糙,例如和拒絕,關閉線程池,關閉後還有沒執行完的任務該怎麼處理這些都是沒有實現的。如果看jdk源碼有點難度可以先看這個,然後再看jdk源碼可能會輕鬆一下,代碼如下
public class MyThreadPool {
/**
* 當前線程數
*/
int threadCount;
/**
* 核心線程數
*/
int coreSize;
/**
* 阻塞隊可容納的任務數
*/
int workerQueueCount;
private ArrayBlockingQueue<Runnable> workQueue;
public MyThreadPool(int coreSize,int workerQueueCount) {
this.workerQueueCount = workerQueueCount;
this.coreSize = coreSize;
this.workQueue = new ArrayBlockingQueue<>(workerQueueCount);
}
public void submit(Runnable task){
if (task == null){
throw new NullPointerException();
}
if (threadCount < coreSize){
System.out.println("創建核心線程執行任務");
MyWorKer myWorKer = new MyWorKer(task);
myWorKer.thread.start();
threadCount++;
}else if (workQueue.size() < workerQueueCount){
try {
System.out.println("加入到阻塞隊列");
workQueue.put(task);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
// jdk線程池不是這樣處理的
System.out.println("直接創建一個線程執行");
new Thread(task).start();
}
}
class MyWorKer implements Runnable {
private Runnable firstTask;
private Thread thread ;
MyWorKer(Runnable firstTask) {
this.firstTask = firstTask;
this.thread = new Thread(this);
}
void runWorker(MyWorKer myWorKer) throws InterruptedException {
Runnable task = myWorKer.firstTask;
// 線程存活,其實就是利用阻塞隊列讓線程阻塞在這個地方
while (task != null || (task = workQueue.take()) != null) {
task.run();
task = null;
}
}
@Override
public void run() {
try {
runWorker(this);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
測試代碼
public class TestMyThreadPool {
public static void main(String[] args) {
MyThreadPool myThreadPool = new MyThreadPool(3,5);
for (int i = 0; i < 6; i++) {
myThreadPool.submit(()->{
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
int i = Thread.activeCount();
System.out.println(i);
}
}
一個任務提交會經歷哪些步驟
這個網上會有很多文章有興趣的可以看一下,jdk源碼的註釋寫的也很明白,源碼也比較簡單
總結就是先判斷是否核心線程數已滿,不滿創建線程執行任務,滿了就判斷阻塞隊列是否已滿,不滿就放入到阻塞隊列中,滿了就判斷當前線程數是否大於最大線程數,大於直接根據拒絕策略拒絕,不大於就創建線程執行
源碼如下,這個源碼還是比較簡單的
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);
}
JDK線程池的工作原理(重點)
認真看過自定義部分代碼的應該會發現,自定義的線程池使用的阻塞的方式是線程阻塞,來保證核心線程數存活(線程的生命週期這種老生常談的問題就不用介紹了,隨便都可以百度的到)
我們看一下jdk的線程池是如何保證核心線程存活的
關鍵代碼
private boolean addWorker(Runnable firstTask, boolean core)
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 {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
t.start()執行的是什麼這個很重要,點進去可以看到真執行的是runWorker方法,runWorker會不停的從阻塞隊列中獲取任務執行
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// getTask是線程阻塞
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);
}
}
看一下take方法
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
// 重點allowCoreThreadTimeOut 是可以設置默認false,只有當線程數大於核心線程數的時候纔會是true
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
// 如果是true就是用可以中斷的方法獲取
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
// 如果是false就是不是中斷的方法獲取任務
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
總的來說,就是allowCoreThreadTimeOut 這個參數來控制線程池是否允許存活,默認是false表示可以存活
彩蛋
核心線程會不會被替換,換言之就是核心線程會一直是之前創建的嗎?後面創建的最大線程有沒有可能成爲核心線程?
寫在後面的話
一個緩解內心迷茫最好的方式就是強迫自己靜下心來學習,只要有收穫就不會感到迷茫