线程池设计原理
一、池化设计
数据池的设计通常运用了工厂,单例和享元三种设计模式。
二、线程池的构建
无论是什么样的线程池,其构架都依托于ThreadPoolExecutor,其核心参数如下(七个):
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数(临时线程数=maximumPoolSize-corePoolSize)
- keepAliveTime:临时线程存活时间
- unit:存活单位
- workQueue:阻塞队列
- threadFactory:线程工厂
- handler:拒绝策略
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
核心原理是生产者消费者模式,有请求就处理,没有就阻塞,所以必须要有员工:核心线程,要有等待队列:阻塞队列,当请求太多,无法消耗,可以扩容:增加临时线程,或者拒绝:拒绝策略。
三、源码解构
一)执行任务execute
创建线程池后,执行任务方法execute,其源码如下:
附:其中的int c是采用位运算判断线程,高3位是线程状态,低29位是线程数量
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);
}
- if (command == null),执行的任务必须是一个线程,要继承了runnable接口
- if (workerCountOf© < corePoolSize)判断核心线程数,因为线程池构建时不会初始化核心线程,所以只有执行任务时才启动线程(类似懒加载,即用的时候再加载), 如果核心线程还有空余,就创建,没有就要加入阻塞队列。
- if (isRunning© && workQueue.offer(command))判断是否加载进阻塞队列,如果可以,就添加,如果不可以,就尝试创建临时线程。
- if (! isRunning(recheck) && remove(command)),判断是否达到最大线程数,如果满了,就拒绝
- if (workerCountOf(recheck) == 0)判断线程是0,执行addWorker(null, false);创建临时线程
- if (!addWorker(command, false))如果临时线程也创建失败了,就要执行拒绝策略reject(command);
归纳流程:
- 核心线程是不是已经创建够了
- 没有,就去创建,然后记入线程池,开始拿阻塞队列里的任务
- 足够了,就把任务添加到阻塞队列里
- 没满,就正常添加
- 满了,判断是否超过最大线程数
- 没有,开始扩容,增加临时线程。
- 满了,拒绝任务
二)addWorker创建线程
创建核心线程时为true,临时线程为false
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
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);
}
return workerStarted;
}
源码相当长,依次看下去
第一个循环中首个判断为:
if (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()))
很简单,如果线程池终止或者阻塞队列为空就没必要创建线程
第二个循环中首个是判断核心线程数还是最大线程数来决定是否允许创建核心还是临时线程,不重要,重要的是第二个:
if (compareAndIncrementWorkerCount©)
如果判断为是,则要终止循环,回到最开始的goto位置,即方法开始的retry:
compareAndIncrementWorkerCount方法源码如下:
private boolean compareAndIncrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect + 1);
}
非常简洁,还是利用了位运算,上文提过的,低29位为计数,所以线程喜加一
循环是因为计数是可能失败的,所以要重新开始。
喜加一之后,就可以开始创建线程了,将firstTask创建
w = new Worker(firstTask);
final Thread t = w.thread;
然后用重入锁防止多线程访问,最后加入到workers中。workers是一个hashset,用作容器池。最后,线程启动,结束创建。
如果失败,就要addWorkerFailed(w);方法,删除喜加一的线程,进行回滚。
当线程启动后,注意这里执行的是work的run方法,而非firstTask的run方法,因此我们要看,work的run方法: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 {
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);
}
}
当然,还是要看判断方法,这里是while的判断:
while (task != null || (task = getTask()) != null)
判断为空无需提,重要的是后者 task = getTask(),task为当前线程,如果线程池中的线程等于当前线程,就执行task.run();方法,即当前线程的run方法,这样就成功得到了阻塞队列里的任务,还没执行任务线程。那么getTask()又如何从阻塞队列中获取任务呢?
四)getTask获取阻塞队列中的任务
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?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
源码很长,但核心其实只有一个
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
workQueue.take();方法,显然是拿任务的,该方法是阻塞方法,可以确保上一个方法的循环始终执行
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :是做什么的呢?我们可以看到一个很眼熟的东西,keepAliveTime,TimeUnit,一个是线程存活时间,一个是线程存活时间单位,所以可以确定,此方法为回收线程的方法。那么需要区分临时线程和核心线程吗?其实不需要,只要保证最后存活的线程数目等于核心线程就可以了,谁被回收无需关心。因此,timed为true,触发销毁
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
两大条件,超时,线程数大于核心线程数,超时代表阻塞队列已经没有等待任务,或者线程数大于核心线程数,那么线程可以被销毁,返回到上一个方法,使循环终止,线程进行到死亡状态,销毁完成。
五)如何拒绝
拒绝策略是灵活的,有多重选择,抛错,当做非线程执行,把旧任务替换掉,甚至可以不做处理
六)合适的线程数目大小
可以尝试用公式:(线程池设定的线程等待时间+线程cpu时间/线程cpu时间)*cpu核心数
1.CPU密集型
保持和cpu数量一致
2.IO密集型
这个可以多设置一些,充分利用cpu性能