Java并发编程:线程池的使用
目录大纲:
* Java中的ThreadPoolExecutor类
* 线程池实现原理
* 线程池状态
* 任务的执行
* 线程池中的线程初始化
* 任务缓存队列及排队策略
* 任务拒绝策略
* 线程池的关闭
* 线程池容量的动态调整
* 自定义线程池
* 合理配置线程池的大小
* FIFO优先级线程池
Java中的ThreadPoolExecutor类
java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。下面我们来看一下ThreadPoolExecutor类的具体实现源码。
在ThreadPoolExecutor类中提供了四个构造方法:
public class ThreadPoolExecutor extends AbstractExecutorService {
//...
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
//...
}
下面介绍各个参数的意义:
corePoolSize: 指的是保留的线程池大小;
maximumPoolSize: 指的是线程池的最大大小;
keepAliveTime: 指的是空闲线程结束的超时时间;
unit: 是一个枚举,表示 keepAliveTime 的单位;
workQueue: 表示存放任务的队列;
threadFactory: 线程工厂,主要用来创建线程;
handler: 表示当拒绝处理任务时的策略;
我们可以从线程池的工作过程中了解这些参数的意义。线程池的工作过程如下:
- 线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
- 当调用 execute() 方法添加一个任务时,线程池会做如下判断:
a. 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
b. 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。
c. 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这个任务;
d. 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”。 - 当一个线程完成任务时,它会从队列中取下一个任务来执行。
- 当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
这样的过程说明,并不是先加入任务就一定会先执行。假设队列大小为 10,corePoolSize 为 3,maximumPoolSize 为 6,那么当加入 20 个任务时,执行的顺序就是这样的:首先执行任务 1、2、3,然后任务 4-13 被放入队列。这时候队列满了,任务 14、15、16 会被马上执行,而任务 17-20 则会抛出异常。最终顺序是:1、2、3、14、15、16、4、5、6、7、8、9、10、11、12、13。
线程池实现原理
下面具体介绍线程池的方法以及属性:
线程池状态
要读懂线程池的状态,需要知道几个属性:
/*
*可以将这个参数看成是一个三十二位的二进制数,
*其中前三位表示线程池的状态,
*后二十九位表示线程池中工作线程的数量
*/
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS; //RUNNING状态表示线程池可以接受任务正常工作
private static final int SHUTDOWN = 0 << COUNT_BITS; //SHUTDOWN状态表示线程池不接受任务,但如果阻塞队列中还有任务,会将阻塞队列中的任务执行完
private static final int STOP = 1 << COUNT_BITS; //STOP状态表示线程池不接受任务,也不会执行阻塞队列中的任务,即使阻塞队列中还存在任务
private static final int TIDYING = 2 << COUNT_BITS; //TIDYING状态表示所有任务都结束了,workerCount为0,调用terminated()方法
private static final int TERMINATED = 3 << COUNT_BITS; //TERMINATED状态terminated()方法调用完成
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; } //获取线程池的状态
private static int workerCountOf(int c) { return c & CAPACITY; } //获取线程池工作线程的数量
private static int ctlOf(int rs, int wc) { return rs | wc; } //用于切换线程池状态,必要的时候改变工作线程的数量
任务的执行
execute方法是线程池执行任务的入口,execute方法的入参是必须要实现了Runnable接口的实现类。在了解任务提交以及任务执行之前,我们先peek一下ThreadPoolExecutor类中其他的一些比较重要成员变量:
private final BlockingQueue<Runnable> workQueue; //任务缓存队列,用来存放等待执行的任务
private final ReentrantLock mainLock = new ReentrantLock(); //线程池的主要状态锁,对线程池状态(比如线程池大小、runState等)的改变都要使用这个锁
private final HashSet<Worker> workers = new HashSet<Worker>(); //用来存放工作集
private volatile long keepAliveTime; //线程存活时间
private volatile boolean allowCoreThreadTimeOut; //是否允许为核心线程设置存活时间
private volatile int corePoolSize; //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
private volatile int maximumPoolSize; //线程池最大能容忍的线程数
private volatile RejectedExecutionHandler handler; //任务拒绝策略
private volatile ThreadFactory threadFactory; //线程工厂,用来创建线程
private int largestPoolSize; //用来记录线程池中曾经出现过的最大线程数
private long completedTaskCount; //用来记录已经执行完毕的任务个数
下面在 看看execute方法:
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();
}
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);
}
在execute方法中没有看到要执行任务的地方,在这个方法里只是做了条件判断,如果满足
条件的话,就交给addWorker处理,并且根据判断条件的不同,传给addWorker方法的参数也不同。总结一下execute方法的执行逻辑:
1.当工作线程数量小于核心线程数量的时候,会将任务交给addWorker方法,addWorker方法会创建新的线程来处理这个任务
2.当工作线程数量大于和核心线程数量并且线程池的工作状态是running的时候,会将任务放入到阻塞队列中
3.当阻塞队列已经满了,会将任务交给addWorker方法处理
4.当交给addWorker方法处理失败或是线程池的状态不是running的时候,会调用线程池的拒绝策越。
execute方法其实是线程池工作的一个大体思路,具体细节我们可以看看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 &&
! (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();
}
//当将任务放到任务队列成功后,启动工作线程t.start(),执行firstTask任务
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
所以,任务的启动是在addWorker方法,而具体的逻辑得看Worker。
为了弄明白t.start(); 我们继续往下看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. */
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);
}
/** Delegates main run loop to outer 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) {
}
}
}
}
代码不多,主要看构造方法,保存了传进来的firstTask,以及通过线程工厂threadFactory实例化一个新的线程,也就是真正的工作线程。
回顾上面的步骤:
1. execute(); 就像一个栅栏,把要执行的任务,根据线程池的配置,划分好是否立刻执行/加入队列,亦或者执行拒绝策略,为后面要做的事情划分清楚;
2. addWorker(); 就是负责开启任务,任务的逻辑封装到Worker内部,我们可以将Worker看成就是一个工作线程,里面包含了执行任务和从阻塞队列中取任务的逻辑(就在runWorker(this)方法里面哦)。
自定义线程池
对于处理些特殊的业务,我们有时会严格要求,发起的每个请求(execute)都是顺序执行的,但是同时有需要具备一定的优先级,也就是FIFO&PRIORIT。
类结构一览:
- Priority 枚举类,标明任务的优先级,可实现优先执行
- PriorityRunnableBase 优先级基础类,排序的依据,实现Runnable, Comparable
- PriorityRunnable 具体业务runnable
- FifoPriorityThreadPoolExecutor工作线程池
FifoPriorityThreadPoolExecutor线程池的构造方法:
//代码片段来自FifoPriorityThreadPoolExecutor
static {
mThreadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.MILLISECONDS,
new PriorityBlockingQueue<Runnable>(), sThreadFactory);
mThreadPoolExecutor.allowCoreThreadTimeOut(true);
}
FifoPriorityThreadPoolExecutor执行任务:
//代码片段来自FifoPriorityThreadPoolExecutor
public void execute(Runnable runnable) {
mThreadPoolExecutor.execute(runnable);
}
设计中最重要的是PriorityRunnableBase,下面看看代码:
/**
* Created by JiangYiDong on 2018/1/19.
*/
public abstract class PriorityRunnableBase implements Runnable, Comparable<PriorityRunnableBase> {
/**
* 数字越大,优先级越高
*/
protected Priority priority;
private int order;
private static final AtomicInteger ordering = new AtomicInteger();
public PriorityRunnableBase(Priority priority) {
this.priority = priority;
this.order = ordering.getAndIncrement();
}
@Override
public int compareTo(PriorityRunnableBase another) {
return this.priority.ordinal() < another.priority.ordinal() ? 1
: this.priority.ordinal() > another.priority.ordinal() ? -1 : this.order - another.order;
}
}
注意:执行任务方法务必使用execute,具体请看线程池execute与submit的区别。