一、线程池的优点
1、降低资源消耗,通过重复利用已创建的线程,降低创建和销毁线程造成的系统资源
2、提高响应速度,当任务到达时,不需要等到线程创建了才执行任务
3、提高管理性,可以统一分配、调优和监控
二、线程池ThreadPoolExecutor的使用
三、线程池ThreadPoolExecutor是如何复用的?
我们知道,Thread.start()只能调用一次,一旦这个调用结束,则该线程就到了stop状态,不能再次调用start,则要达到复用的目的,必须不让它进入stop状态,当它处理完一个任务后,接着从任务队列里取出下一个继续处理,直到任务队列没有任务了才结束线程,这就是线程池的原理。下面我们通过源码来分析
ThreadPoolExecutor执行execute()分4种情况
1、若当前运行的线程少于corePoolSize,则创建新线程来执行任务(执行这一步需要获取全局锁)
2、若运行的线程多于或等于corePoolSize,则将任务加入BlockingQueue
3、若无法将任务加入BlockingQueue,则创建新的线程来处理任务(执行这一步需要获取全局锁)
4若创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()
采取上述思路,是为了在执行execute()时,尽可能避免获取全局锁
在ThreadPoolExecutor完成预热之后(当前运行的线程数大于等于corePoolSize),几乎所有的execute()方法调用都是执行步骤2,而步骤2不需要获取全局锁
execute()方法
int c = ctl.get();
// 当前线程数 < 核心线程数coreSize的话,就新建一个Worker,worker里会新建一条线程
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 能执行到这里,说明当前线程数 >= coreSize。这样的话,就把任务加入队列workQueue。
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任务时,当前线程数肯定是少于核心线程数据,所以会执行addWorker(command,true)。它的关键代码如下:
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;
它会创建一个Worker,然后添加到workers里,然后执行t.start(),这个t就是worker里新建的线程,Worker的关键代码如下:
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker. */
public void run() {
runWorker(this);
}
}
构造函数里持有了我们传进来的runnable,以及创建了一条新的线程,worker本身也实现了Runnable接口,它对firstTask装饰了一层,当调用worker里的线程start()的时候会调用runWorkr(this)方法
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);
}
}
重点就是这个循环了,它是实现线程复用的关键,首先进入循环task是不会空的所以执行循环体,然后获取锁,接着我们看到task.run(),这行就是执行我们传进来的Runnable的run()方法,task.run的前后有两行代码beforeExecute(wt, task);和afterExecute(task, thrown);,它们分别代码任务执行前后,有需要的话我们可以复写这两个方法来监听这两个状态,解锁置空task,这时接着跑下一个循环,这时候task==null,所以跑task=getTask()!=null了,到这里我们就可以推测getTask()的作用就是从BlockingQueue任务队列中尝试获取一个任务,如果能取到就接着跑循环体,如果取不到的话,会阻塞线程,知道有任务或超时,这样一条线程执行了多个任务就达到了复用的目的了。能达到线程复用的一个关键就是BlockingQueue ,workQueue是BlockingQueue的实例字面上理解就是阻塞队列,当workQueue.take()或workQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),如果试图的这两操作无法立即执行,该方法调用的线程将会发生阻塞,直到能够执行或超时,poll(keepAliveTime,TimeUnit.NANOSECONDS)中的keepAliveTime就是线程的存活时长,在这时长里阻塞队列都得不到执行,那么这条线程将会被stop掉,正是这个阻塞,才给任务线程有等待下一个任务到来的机会,从而避免收到工作任务的时候再次新建线程,复用了线程。
四、线程池的BlockingQueue
(https://www.jianshu.com/p/273051b9fae2)
(https://blog.csdn.net/qq_26881739/article/details/80983495)