Github:https://github.com/yihonglei/thinking-in-concurrent
ThreadPoolExecutor是多线程处理工具,可以通过Executors工厂方法进行配置。
ThreadPoolExecutor提供了对线程池管理,避免线程频繁创建和销毁资源消耗,充分利用cpu,
同时也能对系统资源进行控制。
AtomicInteger类型的ctl代表了ThreadPoolExecutor中的控制状态,它是一个原子整数,
在ThreadPoolExecutor里面,借助高低位包装了两个概念:workerCount和runState。
线程池中当前活动的线程数量,它占据ctl的低29位,这样,每当活跃线程数增加或减少时,ctl直接做相应数目的增减即可。
而ThreadPoolExecutor中COUNT_BITS就代表了workerCount所占位数,定义如下:
private static final int COUNT_BITS = Integer.SIZE - 3;
在Java中,一个int占据32位,而COUNT_BITS的结果不言而喻,Integer大小32减去3,就是29;
另外,既然workerCount代表了线程池中当前活动的线程数量,那么它有个上下限阈值,下限是0,上限呢?
ThreadPoolExecutor中CAPACITY就代表了workerCount的上限,它是ThreadPoolExecutor中理论上的最大活跃线程数,
其定义如下:
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
运算过程为1左移29位,也就是00000000 00000000 00000000 00000001 --> 001 0000 00000000 00000000 00000000,
再减去1的话,就是 000 11111 11111111 11111111 11111111,前三位代表线程池运行状态runState,所以这里workerCount的
理论最大值就应该是29个1,即536870911;既然workerCount作为其中一个概念复合在AtomicInteger ctl中,
那么ThreadPoolExecutor理应提供从AtomicInteger ctl中解析出workerCount的方法,如下:
private static int workerCountOf(int c) { return c & CAPACITY; }
计算逻辑简单,传入的c代表的是ctl的值,即高3位为线程池运行状态runState,低29位为线程池中当前活动的线程数量
workerCount,将其与CAPACITY进行与操作&,也就是与000 11111 11111111 11111111 11111111进行与操作,
c的前三位通过与000进行与操作,无论c前三位为何值,最终都会变成000,也就是舍弃前三位的值,
而c的低29位与29个1进行与操作,c的低29位还是会保持原值,这样就从AtomicInteger ctl中解析出了workerCount的值。
线程池运行状态,占据ctl的高3位,有RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED五种状态。
AtomicInteger ctl的定义如下:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
线程池运行状态,它占据ctl的高3位,有RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED五种状态。
1)RUNNING
接受新任务,并处理队列任务。
private static final int RUNNING = -1 << COUNT_BITS;
-1在Java底层是由32个1表示的,左移29位的话,即111 00000 00000000 00000000 00000000,也就是低29位全部为0,
高3位全部为1的话,表示RUNNING状态,即-536870912;
2)SHUTDOWN
不接受新任务,但会处理队列任务。
private static final int SHUTDOWN = 0 << COUNT_BITS;
0在Java底层是由32个0表示的,无论左移多少位,还是32个0,即000 00000 00000000 00000000 00000000,
也就是低29位全部为0,高3位全部为0的话,表示SHUTDOWN状态,即0;
3)STOP
不接受新任务,不会处理队列任务,而且会中断正在处理过程中的任务。
private static final int STOP = 1 << COUNT_BITS;
1在Java底层是由前面的31个0和1个1组成的,左移29位的话,即001 00000 00000000 00000000 00000000,
也就是低29位全部为0,高3位为001的话,表示STOP状态,即536870912;
4)TIDYING
所有的任务已结束,workerCount为0,线程过渡到TIDYING状态,将会执行terminated()钩子方法
private static final int TIDYING = 2 << COUNT_BITS;
2在Java底层是由前面的30个0和1个10组成的,左移29位的话,即010 00000 00000000 00000000 00000000,
也就是低29位全部为0,高3位为010的话,表示TIDYING状态,即1073741824;
5)TERMINATED
terminated()方法已经完成。
private static final int TERMINATED = 3 << COUNT_BITS;
2在Java底层是由前面的30个0和1个11组成的,左移29位的话,即011 00000 00000000 00000000 00000000,
也就是低29位全部为0,高3位为011的话,表示TERMINATED状态,即1610612736;
由上面我们可以得知,运行状态的值按照RUNNING-->SHUTDOWN-->STOP-->TIDYING-->TERMINATED顺序值是递增的,
这些值之间的数值顺序很重要。随着时间的推移,运行状态单调增加,但是不需要经过每个状态。那么,可能存在的线程池状态的
转换是什么呢?
1)RUNNING -> SHUTDOWN
调用shutdownNow()方法后,或者线程池实现了finalize方法,在里面调用了shutdown方法,即隐式调用;
2)(RUNNING or SHUTDOWN) -> STOP
调用shutdownNow()方法后;
3)SHUTDOWN -> TIDYING
线程池和队列均为空时;
4)STOP -> TIDYING
线程池为空时;
5)TIDYING -> TERMINATED
terminated()钩子方法完成时。
我们再来看下是实现获取运行状态的runStateOf()方法,代码如下:
private static int runStateOf(int c) { return c & ~CAPACITY; }
~是按位取反的意思,CAPACITY表示的是高位的3个0,和低位的29个1,而~CAPACITY则表示高位的3个1,2低位的9个0,
然后再与入参c执行按位与操作,即高3位保持原样,低29位全部设置为0,也就获取了线程池的运行状态runState。
最后,我们再看下原子变量ctl的初始化方法ctlOf(),代码如下:
private static int ctlOf(int rs, int wc) { return rs | wc; }
传入的rs表示线程池运行状态runState,其是高3位有值,低29位全部为0的int,而wc则代表线程池中有效线程的
数量workerCount,其为高3位全部为0,而低29位有值得int,将runState和workerCount做或操作|处理,即用runState的
高3位,workerCount的低29位填充的数字,而默认传入的runState、workerCount分别为RUNNING和0。
阻塞队列。当核心线程处理不过来的时候,任务就放入队列里面等待执行。
workers是包含线程池中所有工作线程worker的集合,仅仅当拥有mainLock锁时才能访问它;
completedTaskCount是已完成任务的计数器,只有在worker线程的终止,仅仅当拥有mainLock锁时才能访问它;
线程工厂。用于创建线程池线程。
饱和策略。当线程池达到饱和之后,提交的任务交给饱和策略处理。
线程的存活时间。这个存活时间主要控制的是大于corePoolSize之后创建的线程,如果这部分线程
等待keepAliveTime时间后,还没任务处理,哪么这部分线程就会退出线程池,可以比喻成线程池的临时工,
就像饭店一样,生意火的时候,全职的员工忙不过来,找了些临时工来帮忙,当没啥事干的时候,
这部分临时工就不用了,就像这部分线程退出了一样。
如果通过allowCoreThreadTimeOut设置了允许核心线程退出,这个时间也是核心线程空闲后自动退出的时间。
默认值为false,如果为false,core线程在空闲时依然存活;如果为true,则core线程等待工作,直到时间超时至keepAliveTime;
线程池的核心线程数。每一次任务的提交只要当前线程池已创建线程数小于核心线程数(这里是小于核心线程数可以创建核心线程,
因为这个时候你再创建一个核心线程,刚好达到核心线程的数量,如果是小于等于,你再创建一个核心线程,就超过了核心线程
定义的数量了),都会创建线程执行任务,无论你当前线程池是否有空闲线程。这些线程是全职工,即便没有任务执行,
他们也会等待任务。也即,当线程池里没有任务任务时,也会有corePoolSize个忠诚的线程在等着执行任务。
线程池最大线程池数。无论你提交多少任务,线程池里最多能创建maximumPoolSize个工作线程。
调用线程工厂创建线程池或自定义线程池最后都要调用ThreadPoolExecutor构造方法,
ThreadPoolExecutor有多个重载构造方法,其中一个源代码如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue 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;
}
每个参数的含义,下面源码说明。
线程池工作原理简易图:
1)任务提交到线程池,首先判断线程数是否小于corePoolSize,如果小于,则创建线程执行任务,
无论是否有空闲线程。
2)如果核心线程已满,判断阻塞队列是否已满,如果未满,则将任务加入队列等待执行。
3)如果阻塞队列已满,判断线程池数是否小于maximumPoolSize,如果小于,创建线程执行任务。
4)如果线程池已满,提交的任务交由饱和策略处理(handler)。
下面基于jdk,从源码层面分析线程池工作原理。
源代码中解释任务提交线程池只有三个步骤,但是最后一步可以拆成2个步骤,总共四个步骤个人觉得比较好理解些,
这四个步骤对应上图线程池工作原理图。
public void execute(Runnable command) {
// 如果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();
/**
* 1、任务提交到线程池,首先判断线程池中当前活跃线程数是否小于corePoolSize,如果小于,
* 则调用addWorker()创建工作线程去执行提交的任务。
*/
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
/**
* 2、如果线程池线程数不小于corePoolSize,调用阻塞队列workQueue.offer()
* 往队列添加任务等待执行。
*/
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);
}
/**
* 3、如果放入队列失败,第三步会去创建线程执行任务,在内部判断了如果线程池线程数
* 不小于maximumPoolSize,则创建失败,说明这个时候线程池已经达到饱和状态。
*/
else if (!addWorker(command, false))
// 4、线程池达到饱和状态后,新提交的任务交由饱和策略处理,即reject()方法处理。
reject(command);
}
小结:
1)任务提交小于corePoolSize,则通过addWorker()创建工作线程执行提交的任务;
2)如果corePoolSize已经满了,并都不空闲,则任务添加到阻塞队列;
3)如果阻塞队列也满了,则看是否还能创建工作线程去帮忙执行任务,能,则创建;
4)如果线程池饱和了,则提交的任务交由饱和策略处理;
具体线程的创建工作线程和执行由addWorker()方法完成。
java.util.concurrent.ThreadPoolExecutor#addWorker
这个方法接受一个boolean类型的core变量,标识是否是核心线程创建,如果大家细心的话,
在execute()源码中线程数小于corePoolSize时,调用addWorker()时传进来的是true,队列满了后再调时传的是false。
这个boolean的core取决于线程池线程数是与corePoolSize比较大小,还是与maximumPoolSize
比较大小,来决定能否创建相应的线程。
如果core是true,这个时候是判断是否能创建核心线程,如果线程池数量小于corePoolSize,
则能创建,否则不能创建核心线程;
如果core是false,这个时候是判断是否能再创建线程,如果线程池数量小于maximumPoolSize,
则能创建,否则不能创建非核心线程。
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 {
// 这个地方是创建工作线程的地方,返回一个Worker,worker实现Runnable接口,w.thread拿到的是工作线程,注意,不是我们自己的任务。
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) {
/**
* 看到这个start()方法,是不是有一种久违的感觉,咱们从提交任务到线程池,一路走来,
* 终于看到了线程启动的方法,这个start是Worker的是start,不是我们提交的实际任务的start。
*/
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
Worker继承了AQS框架,同时实现了Runnable接口,让Worker拥有了同步和线程的特性。
private final class Worker
extends AbstractQueuedSynchronizer
implements 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. */
// 这个才是我们提交的哪个业务线程,作为Worker线程的成员变量
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)
*/
/**
* 创建线程,线程工厂传进去的是this,也就是Worker,而不是我们提交的任务,
* 我们提交到线程池的任务被赋值为firstTask,成为了Worker的一个成员属性。
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
/**
* 创建线程,线程工厂传进去的是this,也就是Worker,而不是我们提交的任务,
* 我们提交到线程池的任务被赋值为firstTask,成为了Worker的一个成员属性。
*/
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
/**
* 我们上面已经通过start启动了线程,线程最终会执行对应启动线程的run()方法,
* 即Workder里面的run方法。Worker里面的run()方法调用了runWorker方法,
* 在runWorker里面才是真正执行我们提交的任务的地方。
*/
public void run() {
runWorker(this);
}
// 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) {
}
}
}
}
runWorker正真执行我们提交的任务。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
// 从Worker取成员变量firstTask,即为我们提交到线程池的任务。
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// while写了个死循环,如果task不为空,或者getTask()从队列里面能取到任务,while就不会退出。
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方法,task为我们提交的任务,终于执行了我们提交的任务里面的run方法。
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);
}
}
从阻塞队列获取任务。
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.
/**
* 如果线程池关闭了同时任务状态为Stop或队列为空时,线程数通过CAS算法减少,退出for循环,
* 外层的while也退出,线程退出。
*/
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是一个boolean值。allowCoreThreadTimeOut默认为false,如果wc > corePoolSize,
* 也即线程池线程数大于核心线程数时,从判断条件可以知道,当队列为空,
* 并且TimeOut为True的时候,调用compareAndDecrementWorkerCount(c)
* 减少线程池数,同时,返回null,外层while循环终止,线程释放,退出的是非核心线程。
* 这里需要注意的是TimeOut默认是false,什么时候变成True,在下面分析。
*/
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
/**
* 如果timed为true,是非核心线程通过poll从队列获取任务,超时时间为keepAliveTime,
* 如果获取不到任务,TimeOut就会被设置为True,因为代码在for死循环里面,会触发上面非核心
* 线程退出的代码块。
* 如果timed为false,是核心线程从队列中通过take获取任务,如果没有任务,则阻塞,
* 这就保证了在线程池没有任务的时候,corePoolSize数量的核心线程不退出线程,
* 一直等待任务的执行,因为take拿不到值,就一直阻塞,这就是线程池没有任务执行时
* 还能维持核心线程不退出线程池的本质。
* 因为核心线程数量的线程一直等待任务的执行,这个时候如果再往线程池提交任务,
* 是不会创建线程的,而是任务直接加入队列,阻塞的核心线程就会通过take从队列拿到任务,
* 执行任务,没有任务了,核心线程又恢复阻塞。
*/
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
1)当allowCoreThreadTimeOut=false时,不允许核心线程退出,即会保留核心线程数量的工作线程一直存活,
一直从阻塞队列等待任务执行,则该方法用于退出的就是超出核心线程数量的哪些线程。
2)当allowCoreThreadTimeOut=true时,则允许核心线程退出,则该方法用于退出核心线程和非核心线程。
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
/**
* 移出工作线程,与下面的
*/
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
tryTerminate();
/**
* allowCoreThreadTimeOut是否允许核心线程退出,如果允许,则 min为0,
* workerCountOf(c) >= min为true,就会被return掉,不会走addWorker(null, false);去创建工作线程,
* 核心线程就是通过与上面的workers.remove(w);配合被移出了,没有去创建;
* 如果allowCoreThreadTimeOut设置为false,则min就是corePoolSize,则会创建corePoolSize核心线程,
* 核心线程得以保持住不灭,而非核心线程被上面移出后在下面被return了,则退出。
*/
int c = ctl.get();
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false);
}
}
为了我们快速创建线程池,jdk提供了一些常用场景的线程池创建工厂Executors,底层都是ThreadPoolExecutor。
创建固定大小线程池,构造器如下:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
1)核心线程和最大线程都是入参nThreads,这样设置是避免非核心线程的线程创建和销毁带来性能损耗;
2)keepAliveTime设置为0,这个参数在这里没有意义,只是ThreadPoolExecutor构造必传参数而已,
因为默认是控制非核心线程退出的,但是这里不会创建非核心线程;
3)队列用的是LinkedBlockingQueue(无界阻塞队列,所谓的无界,是因为队列大小设置为int的最大值);
4)应用场景:控制最大并发数,没有空闲线程执行任务时,都放在队列里面等待执行,任务不丢弃,
但是有些任务可能入队晚了,要很久才执行到,如果要求比较及时,需要自定义线程池,把这个队列控制为有界,
队列满了的走饱和策略及时处理。
创建缓冲队列线程池,线程池线程数量自动伸缩,任务越多,线程越多,理论上支持创建操作系统允许创建的线程数,
构造器如下:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
1)核心线程数设置为0,最大线程数设置为int最大值,允许创建出操作系统允许的线程数;
2)keepAliveTime设置为60s,控制非核心线程的退出;
3)阻塞队列用SynchronousQueue,SynchronousQueue不是一个正直的队列,因为他不维护我们提交的任务,
维护的一些线程,这些线程等待元素的加入和移出队列,SynchronousQueue只是作为数据交换的地方,
你要想把东西放上去,你得看看有没有人接手,没人接,你放不进去;
根据ThreadPoolExecutor的execute执行任务逻辑源码,可以知道,当提交第一个任务时,不会去创建核心线程的,
因为设置为0了,也入不了队,因为workQueue.offer(command)这个也会失败,他跟正常的队列不一样,
因为没有空闲线程去等待获取任务,数据交换不了,只能创建线程去执行任务,正常情况下当提交第二个任务时,
如果第一个已经执行完,并且线程还未退出,第二个任务直接走offer,因为有人接手,如果提得太快,
只能不断创建线程执行任务。当没有任务提交,超过keepAliveTime时间,所有线程都会退出。
感觉太绕了,举个生活中的例子!
比如某饭店,厨师做菜,做完菜一般都是放在厨房窗口(假如这个窗口只能放一盘菜,多了放不下,
这个窗口相当于SynchronousQueue,用于数据交换),服务员直接从菜架取菜上菜。
第一回合:当厨师做完第一道菜的时候(提交第一个任务),他会尝试往菜架上放,但是他一看,没有服务员在窗口,
放窗口失败,没人取,放了也白放,他只能喊A服务员接菜(创建线程执行任务),A这个时候从厨师手里接菜,去给
客人上菜;
第二回合:A服务员上完菜之后主动回到窗口,看看有没有要上的菜,他等60s(keepAliveTIme),如果没菜,
他就退出上菜这件事情(线程退出),如果A服务员等的60S内,厨师又做了第二到菜,这个时候厨师一看A服务员在,
二话不说,直接把菜放厨房窗口(workQueue.offer(command)放入成功),A服务员二话不说,直接从窗口端菜上菜;
第三回合:厨师好快,A服务员还没上菜回来,又做了第三道菜,厨师这会看,A服务员还没回来啊,又没法直接放厨房窗口,
这个时候直接叫B服务员来把菜端走上菜(创建新线程执行任务),B直接把菜接走。
第四回合:A服务员回来后等60s,看厨房窗口上没菜,退出上菜这件事(线程退出),B送完菜,也回到窗口,等60s发现
也没菜,也退出上菜这件事情(线程退出),全部线程退出。
4)应用场景:这种线程池模式适合快速执行处理的任务,灵活控制空闲线程的回收,如果每个任务都太长,
就不能像这样无限创建线程处理任务,就像这个饭店,如果每个服务员上菜假如都特别慢,一直把人叫来上菜,
哪整个饭店的人都来送菜,没有人洗碗,没有人擦桌子等,饭店就崩溃了(无限制一直创建线程不回收,
操作系统允许的线程数是有限的,会导致系统崩溃)。
创建一个只有一个核心线程的线程池,任务按队列先进先出顺序执行。
构造器:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
1)核心线程和最大线程都设置为1,控制线程池只有一个线程;
2)队列使用LinkedBlockingQueue无界阻塞;
3)场景就是任务按先进先出的顺序执行任务,主要用于执行少量控制任务执行顺序的场景,缺陷就是,如果并发量比较高,
线程池处理不过来,就不能用这种方式了。
创建定时或周期执行任务的线程池,但是这个一般不用工厂方法,一般是直接通过ScheduledThreadPoolExecutor构造器构建。
simple demo:
package com.jpeony.concurrent.threadpool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 定时或周期执行任务。
*
* @author yihonglei
*/
public class NewScheduledThreadPoolTest {
private final static Logger logger = LoggerFactory.getLogger(NewScheduledThreadPoolTest.class);
public static void main(String[] args) {
// 线程池
ScheduledThreadPoolExecutor ste = new ScheduledThreadPoolExecutor(1);
Runnable runnable = new Runnable() {
@Override
public void run() {
logger.info("ThreadName={},Time={}", Thread.currentThread().getName());
}
};
ste.scheduleAtFixedRate(runnable, 5, 5, TimeUnit.SECONDS);
}
}
Executors工厂方法:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
ScheduledThreadPoolExecutor构造器:
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
public ScheduledFuture> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
if (period <= 0)
throw new IllegalArgumentException();
ScheduledFutureTask sft =
new ScheduledFutureTask(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(period));
RunnableScheduledFuture t = decorateTask(command, sft);
sft.outerTask = t;
delayedExecute(t);
return t;
}
1)核心线程个数可以自定义;
2)任务被包装成ScheduledFutureTask;
3)延时队里是延迟线程实现的根本DelayedWorkQueue,offer处理入队和通知,take处理出队和等待,
本质就是计算下次任务执行时间,通过ReentrantLock的Condition等待和通知机制实现延迟或定期执行。
1、线程池核心概念:任务、核心线程数,工作队列,线程池最大线程数,饱和策略。
2、keepAliveTime和TimeUnit都是针对非核心线程的,只有非核心线程才会存在存活多长时间退出的概念,
但是,如果设置allowCoreThreadTimeOut=true,允许核心线程退出,那么这个参数对核心线程也生效。
3、核心线程在没有任务的时候,通过队列的take一直阻塞,维持线程不退出,一旦有新任务提交线程池,
直接加入到队列,take就能获取到任务执行,所以核心线程能维持等待任务状态,减少线程创建时间,
同时对系统资源进行控制。
4、任务提交后会被包装为Worker对象(工作线程),该对象实现了Runnable接口,实际提交的任务被赋值为
Worker内的firstTask成员变量。Worker调用start方法执行Worker会执行run方法,在run里面调用runWorker方法,
在runWorker里面才是真正调用firstTask的run方法。绕了半天才执行提交的任务的run方法,处理我们的任务逻辑。
5、使用线程池有什么好处?避免线程创建和销毁资源消耗,充分利用cpu,同时也能对系统资源进行控制。