在并发场景下,线程池出镜率是比较高的,线程池的使用可以给我们带来一些好处:
- 降低资源消耗
通过对线程的重复利用避免了频繁的创建和销毁线程所带来的的开销。 - 提高相应速度
当有任务产生时,可以直接使用现有的空闲线程,无需去创建。 - 提高对线程的管理
并发情况下往往会有很多线程运行,但线程不能无限度的创建运行,因为线程也是需要占用资源,过多的线程导致系统可用资源急剧下降,降低系统稳定性。线程池的使用可以帮助我们对线程进行统一分配、调优和监控。
基本使用
public static void main(String[] args) {
// 创建线程池对象
ThreadPoolExecutor pool = new ThreadPoolExecutor(5,
10,
10,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 30; i++) {
// 提交任务
pool.execute(new Runnable() {
@Override
public void run() {
// 线程就输出当前线程名
System.out.println(Thread.currentThread().getName());
}
});
}
}
输出:
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4
pool-1-thread-5
1、ThreadPoolExecutor的组成
- ThreadPoolExecutor的几个核心属性。
/**
* Timeout in nanoseconds for idle threads waiting for work.
* Threads use this timeout when there are more than corePoolSize
* present or if allowCoreThreadTimeOut. Otherwise they wait
* forever for new work.
*/
private volatile long keepAliveTime; // 设置线程最长闲置时间
/**
* If false (default), core threads stay alive even when idle.
* If true, core threads use keepAliveTime to time out waiting
* for work.
*/
private volatile boolean allowCoreThreadTimeOut; // 设置核心线程是否遵循keepAliveTime属性设置
/**
* Core pool size is the minimum number of workers to keep alive
* (and not allow to time out etc) unless allowCoreThreadTimeOut
* is set, in which case the minimum is zero.
* 核心线程数,线程池中保持最小线程数,若设置了 allowCoreThreadTimeOut 为true,则该值最小值为0
*/
private volatile int corePoolSize;
/**
* Maximum pool size. Note that the actual maximum is internally
* bounded by CAPACITY.
*/
private volatile int maximumPoolSize; // 线程池中线程的最大数目
/**
* The queue used for holding tasks and handing off to worker
* threads.
*/
private final BlockingQueue<Runnable> workQueue; // 任务队列
- ThreadPoolExecutor的内部类
/**
* Class Worker mainly maintains interrupt control state for threads running tasks ...
* 线程池中线程执行任务时对应的任务对象
*/
private final class Worker extends AbstractQueuedSynchronizer implements Runnable { ... }
/**
* 任务被拒绝时的处理策略
*/
// 如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行;
public static class CallerRunsPolicy implements RejectedExecutionHandler { ... }
// 该策略会直接抛出异常,阻止系统正常工作;
public static class AbortPolicy implements RejectedExecutionHandler { ... }
// 不处理,丢弃掉
public static class DiscardPolicy implements RejectedExecutionHandler { ... }
// 该策略会丢弃任务队列中最老的一个任务,并执行当前任务
public static class DiscardOldestPolicy implements RejectedExecutionHandler { ... }
2、线程池的基本流程
当有新任务提交时线程池的基本流程如下:
(1)核心线程未满时,创建新线程执行任务。若有空闲线程则空闲线程执行任务;
(2)核心线程满了,阻塞队列未满,则将任务添加到队列中
(3) 若阻塞队列满了,线程池未满时,则创建新的非核心线程执行任务
(4)若线程池也满了,则根据拒绝策略对任务进行处理
具体的执行过程如下图:
3、执行的源码分析
通过线程池对象调用execute()或者submit()方法提交任务开始工作,execute方法适用无返回值的任务,submit方法适用带返回值的任务。
1.我们来看execute方法(submit方法内部也是调用了execute)。
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);
}
2. 添加任务是通过addWorker方法,代码有省略,主要看注释位置。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
...
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 创建Worker任务(Worker实现了Runable)
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
...
if (workerAdded) {
// 线程执行
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
3.这里面主要来看Worker类,Worker类实现了Runable接口。
private final class Worker extends AbstractQueuedSynchronizer
implements Runnable{
/** Thread this worker is running in. Null if factory fails. */
final Thread thread; // 一个线程对象,在构造方法中初始化
/**
* 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;
// 通过线程工厂创建线程对象,创建线程所传入的Runable对象参数即为Worker本身!!!
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
// 省略....
}
主要看注释位置,现在将上面两段代码串联起来,图中说明具体的执行流程
4.runWorker()方法
从上面分析可以看出下面我们要产看runWorker方法。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
// task即为用户使用线程池时提交的任务对象
Runnable task = w.firstTask;
w.firstTask = null; // 将Worker对象中Runable属性之空
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 这个循环的作用就是让线程对队列进行轮询,有任务就取出执行,具体通过getTask()实现
while (task != null || (task = getTask()) != null) {
...
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
// 执行用户定义的Runable对象中的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
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
在runWorker方法中首先会将Worker对象上的Runable对象取出,在while循环中执行。到这其实整个基本的执行流程大致已经清晰了,当然线程池满的情况这里未做分析。
5.getTask()
但是这个方法中有个while循环,那这里为什么要弄一个循环呢?上面注释已经说明了,那就看下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;
}
}
}
/**
* Retrieves and removes the head of this queue, waiting up to the
* specified wait time if necessary for an element to become available.
*
* @param timeout how long to wait before giving up, in units of unit
* @return the head of this queue, or null if the specified waiting time
* elapses before an element is available
* 这个方法表示返回队列中第一个元素,若为等待timeout时长后还未取到则返回空
*/
E poll(long timeout, TimeUnit unit) throws InterruptedException;
/**
* Retrieves and removes the head of this queue, waiting if necessary
* until an element becomes available.
*
* @return the head of this queue
* 返回队列中第一个元素,若取不到元素时会一直等待
*/
E take() throws InterruptedException;
到这我们可以发现在runWorker方法中的while循环会通过这个getTask方法不断地从任务队列中去取任务,并还对核心线程和非核心线程做了区分。在未设置allowCoreThreadTimeOut(默认false)的情况下:
- 核心线程启动执行完一个任务后不会销毁,而是继续从队列取任务,若队列为空则会处于阻塞等待;
- 非核心线程启动执行完一个任务后,也是继续从队列取任务,若队列为空则会等待指定时长,若还未取到任务就会销毁;
若设置allowCoreThreadTimeOut = true,那么线程池中所有的线程都将是执行完一个任务后,会继续从队列取任务,若队列为空则会等待指定时长,若还未取到任务就会销毁;
4、总结
- 关注ThreadPoolExecutor构造方法中参数,有的上面已经说明
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory, // 线程构建工厂,可自定义
RejectedExecutionHandler handler) { // 任务拒绝处理策略
- 线程执行完任务后不会立马销毁,在runWorker()的while循环中可知:
a. 默认情况下,执行完当前任务后会继续从队列中去任务,拿不到任务就死等。非核心线程不同的是完成任务了也会去取任务,若过了指定时长后还没得,那它就over了;
b. 若设置allowCoreThreadTimeOut = true,核心与非核心线程都将会有指定的闲置时间,超出时间就会销毁。 - 核心方法执行流程