一、线程池
在java中,“做事”一般是由线程来做的。在程序运行过程中,每一个请求过来,都会分配一个线程处理请求,处理完之后,线程被销毁。所以说,程序运行中,有很多的线程在不断的被创建和销毁,而线程的创建和销毁会带来大量的系统开销,特别是在请求量巨大的时候,这些开销显得尤为严重。
因此,线程池应运而生,线程池是一种池化技术,用来缓存线程,进行线程的管理,优化和调度,线程池中不仅有线程,还有任务。
例如:
工厂的老板,在生产一件产品时,去外面招一个工人来生产,生产完成后把工人辞退,再次生产,则重新招人。这样的不仅浪费精力时间,而且效率很低。
换种玩法,我先招收5个固定的工人(核心线程),培训好在工位随时待命,有任务来了,5个工人立即工作,工作完成,工人依旧待在岗位待命。
任务来的比较多话,先放在箱子里(阻塞队列)等待工人腾出手来处理,如果箱子放满了,实在放不下了。但是老板想多挣钱呀,所以算了一下,再招点临时工吧(非核心线程 最大线程数),招多少呢?算啦,把剩下的工位都招满吧,虽然要多付工资,但是与收益相比就不算什么了 ,不过考虑到成本问题,干完这一票,还得把这批人辞退。
过段时间,老板发现生产的产品销售太好,又来了一大批订单,这时老板发现,工厂工人全部全力生产还是来不及,没办法,只能拒绝掉一批订单了,为啥?没有坑位在招人了呀。
而线程池就类似于工厂,线程池的工作原理和工厂中工人工作逻辑一样。
但是,还有一个问题需要考虑,比如工厂老板接只接到了几个任务,并且这几个任务每个都需要工作一年甚至更久才能完成,那么老板还需要招几个固定工人在那待命吗?毕竟工人空闲的时候也是要付工资的。这样一算,反而是临时招人完成任务后辞退更划算。
所以什么情况下适合线程池?
- 任务多
- 单个任务执行时间短
二、线程池的使用
java中提供了一个Executors创建线程池的框架,使用Exe线程池cutors可以创建一个,而Executors框架为我们提供了创建四种线程池的方式:
- Executors.newCachedThreadPool():创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
可以看到,其核心线程为0,并且线程空闲可存活时间为60s。
- Executors.newFixedThreadPool(1):创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
可以看到,其核心线程和最大线程一样,也就是说线程池中最多且一直就这么多线程。
- Executors.newSingleThreadExecutor():创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
线程池中只有唯一的一个线程在工作。
- Executors.newScheduledThreadPool(1):创建一个定长线程池,支持定时及周期性任务执行。
总结:使用Executors框架,可以非常方便的创建一个线程池,使用非常简单 (创建线程池完成返回一个ExecutorService类的实例,通过调用execute()或者submit()方法就可以了)。
但其实我们并不建议使用该工具创建线程池,因为,我们并不能很好利用线程池为我们提供的一系列的参数,例如:核心线程,最大线程,阻塞队列,拒绝策略,超过核心线程数的其他线程的空闲存活时间等,这就导致我们无法灵活使用线程池,所以我们建议使用ThreadPoolExecutor创建线程池。阿里的开发规范中也是禁止使用Executors创建线程池的。
特别说明:execute()和submit()是有点区别的,下面我们来看看这两个方法的入参:
void execute(Runnable command);//传入的是一个Runnable的任务。
<T> Future<T> submit(Callable<T> task);//传入的是一个Callable类型的带有返回值的任务。
<T> Future<T> submit(Runnable task, T result);//传入的是一个Runnable的任务 虽然有返回值,但是Runnable类型的任务的返回值为null.换句话说,通过Future.get()方法获取Runnable的任务结果为null。
execute():只能接受Runnable类型的任务
submit():不仅可以接受Callable类型的任务,还可以接受Runnable类型的任务,并且在接受Callable任务是可以有返回值。
从上图中可以看出,大部分构造方法中都是创建了一个ThreadPoolExecutor对象,即使定时线程池最终也是创建了一个ThreadPoolExecutor对象,所以下面我们重点从ThreadPoolExecutor类分析线程池实现原理。
三、线程池的实现原理
线程池的重要参数说明
corePoolSize:核心线程数,一般常存线程池中不被销毁的线程的数量(除:配置了允许核心线程空闲时被销毁),超过核心线程数再来任务会被放入阻塞队列。
maximumPoolSize:最大线程数,线程池中最多只能有这么多的线程工作,如果阻塞队列满了,无法放入阻塞队列,再来任务,则会创建新的线程去执行任务(线程总数<=maximumPoolSize原则不变)。
workQueue:阻塞队列,存放核心线程无法及时处理的任务,任务阻塞在队列中,等待核心线程空闲时以遵循FIFO原则取出任务处理。
keepAliveTime:允许空闲线程的存活时间,一般是对于核心线程之外的线程,当然如果设置了允许核心线程空闲超时销毁,这个参数也适用于核心线程。
RejectedExecutionHandler:拒绝策略,当线程池中的工作线程以达到maximumPoolSize数,再过来的任务,线程池会执行拒绝策略拒绝掉任务,不在处理。
线程池的重点属性ctl
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
ctl是线程池控制运行状态和有效线程数量的一个重要字段,它包含了两部分信息:高3位保存的是线程池运行状态(runState),低29位保存的是线程池中有效线程数量(workerCount)。
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
可以看到ctl是Integer类型,COUNT_BITS就是低29位,而CAPACITY 是1向左移动29位 -1,这个常量表示workerCount的上线值大概是5亿。
线程池的状态说明
RUNNING = -1 << COUNT_BITS:高三位111,值为-536870912。线程池处于RUNNING状态时能够接受新的任务并且对已添加的任务进行处理。此状态为线程池的初始化状态。
SHUTDOWN = 0 << COUNT_BITS:高三位000,值为0。线程池处于SHUTDOWN状态,不在接受新的任务,但是可以继续处理已添加的任务。当调用shutDown()方法时,线程池RUNNING->SHUTDOWN。
STOP = 1 << COUNT_BITS:高三位001,值为536870912。线程池处于STOP状态,不在接受新的任务,也不会处理已添加的任务,并且会中断正在处理任务。调用线程池的shutDownNow()方法时,线程池RUNNING/SHUTDOWN->STOP。
TIDYING = 2 << COUNT_BITS:高三位010,值为1073741824。当线程池中的任务全部停止,且ctl记录的线程数量变为0时,线程会处于TIDYING状态,当线程处于此状态时,会调用钩子函数terminated(),此方法是一个空方法,用户可以通过重载此方法进行相应的处理。首先当线程池处于SHUTDOWN状态时,阻塞队列为空,并且工作线程数为0,线程池会由SHUTDOWN->TIDYING。
TERMINATED = 3 << COUNT_BITS:高三位011,置为1610612736。线程池彻底终止,当线程池处于TIDTING状态时,调用完钩子函数terminated()后,线程池由TIDYING->TERMINATED。
实现原理
首先以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();//创建失败(可能原因线程池状态或者已到核心线程数) 重新获取ctl的值
}
if (isRunning(c) && workQueue.offer(command)) {//判断线程池状态RUNNING,并加入阻塞队列
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))//再次判断线程池状态,若不是RUNNING,则移除刚才加入队列的任务
reject(command);//执行拒绝策略
else if (workerCountOf(recheck) == 0)//线程池状态RUNNING,工作线程数为0,
addWorker(null, false);//创建线程执阻塞队列中的任务
}
else if (!addWorker(command, false))//队列满了 创建非核心线程执行任务
reject(command);//创建非核心线程失败(可能原因线程池状态或者已到最大线程数),执行拒绝策略
}
代码逻辑展示如下:
线程池接受到一个任务大概做如下处理:
工作线程数<核心线程数,则新建核心线程处理任务。
工作线程数=核心线程数,新来的任务加入阻塞队列。
核心线程数<=工作线程数<最大线程数,且阻塞队列满了,创建非核心线程去处理新任务(非阻塞队列中的任务)
工作线程数=最大线程数,执行拒绝策略。
下面着重分析一下 addWorker方法,先看代码:
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 &&//状态>=SHUTDOWN表示此时不再接受任务,
//前面说过: SHUTDOWN状态不接受新任务,但仍然执行已经加入任务队列的任务,所以当进入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))//CAS操作添加线程数
break retry;//成功调处外层循环
c = ctl.get(); // Re-read ctl 重新读取状态
if (runStateOf(c) != rs)//
continue retry;//状态变了,跳出外层循环
// else CAS failed due to workerCount change; retry inner loop
//否在在内层循环中不停的尝试CAS操作添加线程数
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//创建一个工作线程对象,内部封装了任务,线程对象 重写run方法
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;//获取锁对象
mainLock.lock();//加锁 下面有对临界资源workers的操作 保证线程安全
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)) {//或者shutDown状态但是没有新任务
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);//添加workers HashSet集合
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;//largestPoolSize记录着线程池中出现过最大线程数量
workerAdded = true;
}
} finally {
mainLock.unlock();//解锁
}
if (workerAdded) {
t.start();//启动线程
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
addWorker()方法简单的说 就是创建一个Worker对象,这个对象中封装了Runnable任务和Thread线程(通过线程工厂创建的线程),然后调用start()方法启动线程。Worker承载了线程池运行的最核心的一部分,下面分析一下这个对象
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable//重点!!!!!,Worker 实现了Runnable接口
{
/**
* This class will never be serialized, but we provide a
* serialVersionUID to suppress a javac warning.
*/
private static final long serialVersionUID = 6138294804551838833L;
/** Thread this worker is running in. Null if factory fails. */
final Thread thread;
/** Initial task to run. Possibly null. */
Runnable firstTask;
/** Per-thread task counter */
volatile long completedTasks;
/**
* 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);//通过线程工厂创建一个线程,并把自己作为一个任务传给线程,当线程启动时,会调用worker的run()方法
}
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);//重写run()的逻辑 核心逻辑
}
// Lock methods
//
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
线程池中的线程被封装为一个Worker对象,而线程池就是维护了一组Worker对象
Worker实现了Runnable接口,所以worker本身可以作为一个任务传给线程,线程启动后会调用worker的run()方法,而线程的工作逻辑以及线程的复用都在runWorker(this)中体现。
我们可以看到其构造方法中,接受了传入的任务对象,和创建了一个线程,并且调用了一个setState(-1)的方法。
为什么调用setState(-1),我们可以看到Worker继承了AQS框架,调用setState(-1)是把同步状态修改为-1,那么为什么这么做呢?为什么不使用ReentrantLock呢? 先看一下runWorker(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);
}
}
首先可以看到方法一上来就调用了unlock()方法
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
目的是把state设置为0,我们知道,当一个线程在被创建后还未执行任务 或正在执行任务时,线程是不允许被中断的。
而如果调用了线程池的shutDown()方法,方法内部会调用interruptWorkers()方法
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
public boolean tryLock() { return tryAcquire(1); }
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
这个方法内部调用tryLock()-->tryAcquire()这个方法来判断是否可以中断线程,只有当state为0的时候CAS操作才有可能成功,从而线程才能被中断。
所以一开始在Worker的构造方法中初始设置state为-1,就是为了防止线程在创建之后还未执行任务呢就被中断了。
那runWorker(this)方法中一上来就调用unlock()方法就很容易理解了,毕竟到此时线程已被启动了,紧接着就是获取任务执行任务了,在获取到任务之后,会调用worker.lock()上锁,所以要先把state设置为0才能上锁呀,一旦上锁成功,则线程开始执行任务。
而ReentrantLock在初始化的时候就把state初始化为0,显然并不符合线程池的要求,且ReentrantLock是支持可重入的,而线程池是不需要重入特性的,为什么?看一个方法
public void setCorePoolSize(int corePoolSize) {
if (corePoolSize < 0)
throw new IllegalArgumentException();
int delta = corePoolSize - this.corePoolSize;
this.corePoolSize = corePoolSize;
if (workerCountOf(ctl.get()) > corePoolSize)
interruptIdleWorkers();
else if (delta > 0) {
// We don't really know how many new threads are "needed".
// As a heuristic, prestart enough new workers (up to new
// core size) to handle the current number of tasks in
// queue, but stop if queue becomes empty while doing so.
int k = Math.min(delta, workQueue.size());
while (k-- > 0 && addWorker(null, true)) {
if (workQueue.isEmpty())
break;
}
}
}
这个方法也调用了interruptIdleWorkers();内部也会调用tryLock()方法,想象一下,当前线程执行的任务内容调用setCorePoolSize()方法了,那么如果使用ReentrantLock就支持了可重入性,支持了重入性,就说明tryLock()返回的是true,那么当前线程就要被阻断了,而此时线程是不能阻断的,所以不能设置可重入,因此这里继承了AQS重写tryLock()方法。
以上对继承AQS和runWoker()方法里先unlock后lock以及构造方法中setState(-1)做一下说明。
好了,接着说一下runWorker()方法。
可以看到此方法上来就是一个while (task != null || (task = getTask()) != null) {}循环,而这个循环就是线程可以复用的一个体现,
当线程第一次启动执行完传入的任务之后,会进入下一轮循环,此时需要调用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;//如果允许核心线程空闲超时销毁或者工作线程大于核心线程 timed=true
//大于最大限制线程数或超过空闲时间,并且当前线程数大于1或队列为空
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))//说明线程数减一成功,返回null,意味着一个线程会退出
return null;
continue;//上面线程数减一失败,说明线程数量已被抢先改变,继续循环,
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();//核心线程且并未设置允许核心线程空闲超时销毁 调用take()方法,可以阻塞直到获取任务为止,超过核心线程的其他线程调用poll()方法超时返回null
if (r != null)
return r;
timedOut = true;//核心线程超时或者核心线程之外的线程超时,进入下一轮循环 然后进行线程数-1 返回null 此线程退出线程池
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
getTask()方法可以看到是一个死循环,正常情况下 核心线程会调用阻塞队列的take()方法获取任务,如果队列为null,线程会一直阻塞在take()方法中,一直等到新的任务到来。
如果设置允许核心线程空闲等待,或者核心线程之外的其他线程 会调用poll()方法阻塞等待keepAliveTime这个时间后,如果还未取到任务执行,将返回null,timeOut参数设置为true,进入下一轮循环,进入中间的一段逻辑,线程数减一,返回null,跳出死循环,返回到runWorker()方法,之后跳出runWorker()方法的while循环进入processWorkerExit(w, completedAbruptly);把当前线程移除线程池,线程结束。
综上,核心线程的复用,以及核心线程之外的其他线程执行完任务后被踢出线程池的相关逻辑到这里就分析完了。
从上面的分析可以看出,核心线程也是可以允许空闲时间超时被销毁的,只需要调用ThreadPoolExecutor的allowCoreThreadTimeOut(boolean value)方法就可以了,默认是false。