在开始之前得先了解线程池创建线程以及使用的流程
- 根据corePoolSize创建初始线程, 只要有新的任务委派给线程池执行的, 就会通过线程工厂(Executors)创建一个新的线程
- 如果当前线程数=corePoolSize, 那么就将后续的任务加入阻塞队列, 等待worker完成任务之后从阻塞队列的队列头开始将任务出列执行
- 如果阻塞队列已经满了, 那么就会根据设置的maximumPoolSize来判断, 如果当前线程数小于maximumPoolSize则通过线程工厂创建新线程, 否则进行饱和策略
- 饱和策略就是拒绝方式, 可以选择抛出异常, 忽略, 放弃时间最早的任务转而执行新任务, 直接由提交任务的线程执行任务这四种拒绝方式
这里是我自制的execute方法流程图
main方法
先贴上我做实验使用的main方法
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
int corePoolSize = 2;
int maximumPoolSize = 4;
BlockingQueue<Runnable> blockingDeque = new ArrayBlockingQueue<>(2);
RejectedExecutionHandler handle = new ThreadPoolExecutor.AbortPolicy();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 0,
TimeUnit.SECONDS, blockingDeque, handle);
Thread[] threads = new Thread[6];
for(int i = 0; i < 6; i ++) {
int finalI = i;
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("start: " + finalI);
Thread.sleep(1000);
System.out.println("end: " + finalI);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
for(Thread thread: threads) {
threadPoolExecutor.submit(thread);
}
System.out.println(threadPoolExecutor.getActiveCount());
threadPoolExecutor.shutdown();
}
}
构造器
这里只贴一个, 其他构造方法都是直接使用这个的
参数名称 | 作用 |
---|---|
corePoolSize | 初始线程池的线程个数 |
maximumPoolSize | 线程池中最大线程个数 |
keepAliveTime | 线程存活时间 |
unit | 存活时间的单位 |
workQueue | 一个阻塞队列 |
threadFactory | 线程工厂 |
handler | 采取的饱和政策(拒绝的策略) |
拒绝策略 | 行为 |
---|---|
AbortPolicy | 拒绝时会抛出RejectedExecutionException异常 |
DiscardPolicy | 直接忽略 |
DiscardOldestPolicy | 丢弃执行队列中最老的任务, 尝试提供位置给当前任务 |
CallerRunsPolicy | 交给任务提交者执行 |
拒绝策略默认为AbortPolicy, 构造器中前五个参数都是必填参数, 线程工厂默认为Executors中的defaultThreadFactory
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> 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;
}
Worker内部类
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. */
Runnable firstTask;
/** Per-thread task counter */
volatile long completedTasks;
// 构造器, 从工厂中获取新线程
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
// 这个任务的run方法调用在ThreadPoolExecutor中的runWorker, 后面会写到
public void run() {
runWorker(this);
}
// Lock methods
//
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
// 以下都是重写AQS方法来实现锁
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) {
}
}
}
}
executor
executor是将任务提交到线程池, 这是使用线程池的第二步, 第一步当然是new一个线程池
这个方法里面有三个判断
- 当前正在执行的任务小于corePoolSize, 也就是线程池还有空余, 那么直接执行任务, 否则进入2
- 将当前任务放入阻塞队列, 等待直至线程池中有空闲线程
- 如果阻塞队列满了, 就会判断之前设置的maximumPoolSize最大线程个数, 如果当前执行的任务个数小于maximumPoolSize, 那么就会创建新线程去执行这个任务
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// ctl初始值是一个很大的负数, 这个负数同CAPACITY进行与运算可以得出正在运行的任务个数
int c = ctl.get();
// 这是第一个判断
if (workerCountOf(c) < corePoolSize) {
// 在addWorker中通过true和false来判断这是第一步还是第三步
if (addWorker(command, true))
return;
c = ctl.get();
}
// isRunning方法用于判断线程池是否还在工作
// 如果还在工作就将任务加入阻塞队列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 重新进行工作检测, 在并发情况下可能会出现工作状况被别的线程修改的情况
// 如果线程池结束工作了并且阻塞队列中移除当前任务成功, 则进行饱和策略
if (! isRunning(recheck) && remove(command))
reject(command);
// 否则判断当前正在执行的任务个数是不是0
// 如果是则添加一个空任务
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 阻塞队列满了, 第三步
else if (!addWorker(command, false))
// 添加任务失败则进行饱和策略
reject(command);
}
addWorker
这个方法是线程池中很重要的方法, 用于执行任务
private boolean addWorker(Runnable firstTask, boolean core) {
// break标记
retry:
for (;;) {
int c = ctl.get();
// 获取当前线程池状态
int rs = runStateOf(c);
// 如果rs是终止状态或者其他三种状态(这些状态都不接受新任务)并且
// rs不是终止状态或者传入的任务为空, 或者阻塞队列为空
// 就会进入语句块, 返回false
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
// wc是线程正在执行的任务个数
// 如果wc大于线程池所能承受的最大数量CAPACITY = 536,870,911(二进制中29个1)
// 否则, 看是executor的第几步来判断是否大于corePoolSize 或者maximumPoolSize
// 如果上述两个条件满足一个则返回false
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 使用cas操作将正在执行的任务+1, 成功则跳出最外侧循环
if (compareAndIncrementWorkerCount(c))
break retry;
// cas失败了, 则更新c的数值
c = ctl.get(); // Re-read ctl
// 判断rs状态是否改变
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 {
// 获取rs的状态
int rs = runStateOf(ctl.get());
// 这里不需要进行跟上面一样的重复判断, 因为这里是同步的
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 判断t线程是否已经存在, 因为这里需要启动线程
if (t.isAlive())
throw new IllegalThreadStateException();
// 将w加入到workers中, workers是一个被final修饰的hashSet
// 这里提一句被final修饰的类或者对象我们是可以改变其内部值的
// final修饰对象时仅仅是不可改变其引用地址
// 因为workers的所有方法在使用前都会加上锁, 所以这里可以直接获取size
workers.add(w);
int s = workers.size();
// largestPoolSize初始是0, 用于记录最大的同时执行任务数
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// 任务添加成功就执行线程
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
// 任务执行失败就调用添加任务失败方法来移除任务
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
runWorker
在runWorker中已经创建的Worker会一直存在, 并且会自旋, 获取阻塞队列中的任务执行就是在这个方法中, Worker最大值就是之前设置的最大线程数
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 这里的循环如果初始传入的Worker中无任务, 则使用getTask方法从阻塞队列中获取
// getTask方法里面如果返回null则会中止线程, keepAliveTime参数在这里有用到
while (task != null || (task = getTask()) != null) {
// 在这里就可以看见work被锁上了
// 所以worker继承aqs的作用就是为了防止任务执行时被中断
// 所以不论是shutdown还是shutdownNow, 都无法在worker执行的时候终止worker
w.lock();
// 当前线程池是否大于STOP状态, 或者当前线程需要中断并清除中断状态, 并且
// 这时的线程池状态是大于STOP的
// 如果上述条件都满足, 那么检查该对象是否处于中断状态, 如果不处于则设置中断
// 这里是保证线程池处于中断状态的时候, 一定设置了线程的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, 该Worker的完成任务数量+1
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
// task为空并且阻塞队列中无任务则关闭该Worker
processWorkerExit(w, completedAbruptly);
}
}
不难发现, 只要阻塞队列中还有任务, 已经存在的Worker就不会关闭, 而是去执行阻塞队列中的任务
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 这个标志在上文中, 如果是正常的try流程则会为false
// 如果是异常了就会是true, 这里就会进入decrementWorkerCount方法
// 使用cas操作将ctl- 1
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 更新总完成数, 在hashSet中移除该Worker
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
// 判断线程池状态尝试终止线程池
tryTerminate();
int c = ctl.get();
// 如果当前线程池状态是不需要被关闭的, 即小于STOP的
if (runStateLessThan(c, STOP)) {
// 判断是否正常中止, 如果是则说明所有任务都已完成就进入该语句块
if (!completedAbruptly) {
// min默认是corePoolSize, 如果允许超时则是0
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
// 如果min是0阻塞队列是不为空的, 那么必须要保证有一个工作线程
if (min == 0 && ! workQueue.isEmpty())
min = 1;
// 如果当前线程数量大于等于最小工作线程数量则直接返回
if (workerCountOf(c) >= min)
return; // replacement not needed
}
// 因为不是正常中止, 所以该Worker不该被中止, 添加一个空的Worker
// 这样可以继续执行阻塞队列中的任务
addWorker(null, false);
}
}
getTask
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
// 这是一个死循环, 即必须返回一个元素
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 判断线程池状况, 如果下面情况同时满足则将ctl - 1
// 1.状态>=SHUTDOWN, 即需要被中止
// 2.状态>=STOP, 即需要被中止并且不完成已添加的任务, 或者阻塞队列为空
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
// 获取工作线程个数
int wc = workerCountOf(c);
// 判断工作线程个数是否大于核心线程个数, 那临时线程是可以超时销毁的, 所以timed是true
// allowCoreThreadTimeOut参数为是否允许核心线程超时, 默认为false
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 如果工作线程数量大于最大线程数量, 或者
// timed是true并且timedOut是true(允许销毁核心线程或者临时线程的poll等待超时)
// 并且工作线程个数大于1或者阻塞队列为空
// 如果阻塞队列非空那么一定要保证有多余一个线程可以执行任务, 所以做这么个判断
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
// 满足条件就将wc-1, 因为wc大于最大线程数量或者已经超时
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// timed为true则将阻塞队列用poll加上等待时间出列,返回到runWorker的while中执行
// 如果是核心线程则默认不销毁,直接使用take()
// 这里的poll是根据参数keepAliveTime来判断, 若等待时间超出
// 即阻塞队列中无任务的时间超出keepAliveTime就返回null,
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
// 返回r, 可以给Worker执行
return r;
// 这里设置timedOut=true, 然后重新自旋
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
后记
在学习ThreadPoolExecutor源码的过程中遇到最大的问题就是, 一直在想阻塞队列中的任务是如何执行的, 线程池为什么可以节省创建线程的时间, 线程池中是否创建的时候就自带线程, 这些问题在学习完源码之后就都解决了