目录
ExecutorService
submit重载方法
线程池有有两种提交任务的方法一种是execute
由根接口Executor提供,第二种是ExecutorService提供的submit
方法 该方法有三种重载方法,我们完全可以拿submit方法替代execute方法;不过本质上这四种方法最后都殊途同归都是调用execute方法
submit执行的任务都是异步执行返回值为Future我们可以通过Future .get();获取异步任务执行的结果
具体实现在AbstractExecutorService当值
/**
* 提交有返回值的任务以供执行,并返回表示任务挂起结果的Future。
* Future的get方法将在任务成功完成后返回任务的结果。 (常用方法 具体异步任务有返回值)
*
如果要阻塞等待任务,可以使用result = exec.submit(aCallable).get();
任务未完成 get方法 会阻塞等待任务完成返回结果
*
*
* @param task 要提交的任务
* @param <T> 任务结果的类型
* @return a Future 表示任务即将完成的Future
* @throws RejectedExecutionException if the task cannot be
* scheduled for execution
* @throws NullPointerException if the task is null
*/
<T> Future<T> submit(Callable<T> task);
/**
提交要执行的可运行任务,并返回表示该任务的Future。
Future的get方法在成功完成后将返回null。
? 表示Object
*
* @param task 要提交的任务
* @return a Future 表示任务即将完成的未来
* @throws RejectedExecutionException if the task cannot be
* scheduled for execution
* @throws NullPointerException if the task is null
*/
Future<?> submit(Runnable task);
/**
提交要执行的可运行任务,并返回表示该任务的Future。
Future的get方法将在成功完成后返回给定的结果(result第二个参数就是给定的结果)。
意思该返回的结果是提前设置好的。
* @param task 要提交的任务
* @param result 返回结果
* @param <T> 结果的类型
* @return a Future 表示任务即将完成的未来Future
* @throws RejectedExecutionException if the task cannot be
* scheduled for execution
* @throws NullPointerException if the task is null
*/
<T> Future<T> submit(Runnable task, T result);
总结: 对于线程池来说,其提供了execute与submit两种方式来向线程池提交任务,总体来说,submit方法是可以取代execute方法的,因为它既可以接收Callable任务,也可以接收Runnable任务.
线程池的执行策略(重要)
关于线程池的执行策略有以下三点:
- 如果线程池中正在执行的线程数(包含0) < corePoolSize, 那么线程池就会优先选择创建新的线程而非将提交的任务加到阻塞队列中.
- 如果线程中正在执行的线程数 >= corePoolSize, 那么线程池就会优先选择对提交的任务进行阻塞排队而非创建新的线程.
- 如果提交的任务无法加入阻塞队列当中, 那么线程池就会创建新的线程; 如果创建的线程数超过了maximumPoolSize,那么拒绝策略就会起作用.
submit重载方法实现
该方法实现位于AbstractExecutorService抽象类当中
RunnableFuture
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
//生成一个可运行的可返回执行结果的的对象,将任务赋值个TaskFuture
RunnableFuture<T> ftask = newTaskFor(task, result);
// execute(ftask) == this.execute(ftask) 等同于执行当前线程池的execute方法
execute(ftask);
//返回对应的Future具体查看执行结果可以通过get方法查看
return ftask;
}
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
本质上newTaskFor
其实底层都是执行FutureTask 将runnable转换成callable
在这我们也可以看出上述三个重载方法底层都将会将Runnable任务转换成Callable 任务 返回值就是传进来的 result 。 本质上三个方法都是一样的。
线程池任务提交总结
- 两种提交方法: submit与execute
- submit有三种方式,无论哪种方式,最终都是将传递进来的任务转换成一个callable对象进行处理.
- 当Callable对象构建完毕后,最终都会调用Executor接口中声明的execute方法进行统一的处理
ThreadPoolExecutor.execute
线程池执行最根本的方法
在将来的某个时间执行给定的任务。任务可以在新线程或现有池线程中执行(可以使用池中已有的线程或者线程池新生成的线程)。如果任务无法提交执行,或者因为此执行器已关闭或已达到其blockingQueue容量,则该任务将由当前RejectedExecutionHandler处理。
Execute执行时分三步进行(执行策略):
1.如果运行的线程少于corePoolSize,请尝试以给定的命令execute
作为第一个启动新线程任务。对addWorker的调用原子性地检查运行状态和workerCount,以防止错误警报如果不应该的话,则返回false。
2.如果任务可以成功排队(Queue),那么我们仍然需要再次检查是否应该添加线程(因为上次检查后已有的线程消亡或关闭了)或者池自进入此方法后关闭。所以我们重新检查状态,必要时回滚排队已停止,或启动新线程(如果没有线程)。
3.如果无法对任务进行排队,则尝试添加新的线程。如果它失败了,我们知道我们是关闭或饱和所以拒绝任务。
线程池的状态类型
对于线程池来时, 存在两个状态需要维护:
- 线程池本身的状态: ctl的高3位来表示
- 线程池中所运行着的线程数量: ctl的其余29位来表示
线程池5种状态
(状态不是只按照顺序执行,中间状态可以跳过的)
1.RUNNING: 线程池可以接收新的任务提交, 并且还可以正常处理阻塞队列中的任务.
2.SHUTDOWN: 不在接收新的任务提交, 不过线程池可以继续处理阻塞队列中的任务.
3.STOP: 不再接受新的任务提交, 同事还会丢弃阻塞队列中既有任务; 此外,它还会中断正在处理中的任务.
4.TIDYING: 所有的任务都执行完毕后(同时也涵盖了阻塞队列的任务), 当前线程池中的活动的线程数量降为0, 将会调用terminated方法.
5.TERMINATED: 线程池的终止状态, 当terminated方法执行完毕后, 线程池将会处于该状态之下.
状态之间的转换
RUNNING -> SHUTDOWN: 当调用了线程池的shutdown方法时, 或者当finalize方法被隐式调用后(该方法内部会调用shutDown方法)
RUNNING, SHUTDOWN -> STOP: 当调用了线程池的shutdownNow方法时.
SHUTDOWN -> TIDYING: 在线程池与阻塞队列均变为空时.
STOP -> TIDYING: 在线程池中活动线程数量变为空时.
TIDYING -> TERMINATED: 在terminated回调方法执行完毕之后
Execute方法源码逻辑
/**
* @param command 执行的任务
* @throws RejectedExecutionException at discretion of
* {@code RejectedExecutionHandler}, if the task
* cannot be accepted for execution
* @throws NullPointerException if {@code command} is null
*/
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.
*/
//ctl 对应线程池的两个状态,1.线程池本身的状态(ctl高3位),2.线程池中线程的数量(ctl其余29位)
int c = ctl.get();
/*
workerCountOf(c): 获取当前线程池的线程数量 小于核心数量吗
*/
if (workerCountOf(c) < corePoolSize) {
/*当小于核心数
添加一个新的线程 并把任务交给线程执行
*/
if (addWorker(command, true))
//当执行成功 返回
return;
//(线程池被多个线程执行)失败 否则重新获取更新线程状态(两个 存在其他线程更改了状态)
c = ctl.get();
}
/*
isRunning(c) 线程池状态是否正在运行
并且workQueue.offer(command) 任务是否能插入队列当中
*/
if (isRunning(c) && workQueue.offer(command)) {
//标识当前线程池赈灾运行且任务插入到了阻塞队列中
//获取ctl最新值
int recheck = ctl.get();
/*
回滚操作 && 前为真 后则不执行
如果线程池不为RUNNING为真 则将当前任务在队列中删除
*/
if (! isRunning(recheck) && remove(command))
//执行拒绝策略
reject(command);
//上述判断为真时 此时会继续判断 当前的线程池的可用线程数量是否为0 没有则线程无可用
else if (workerCountOf(recheck) == 0)
//则创建一个新的线程 由于任务被放置到阻塞队列只需要创建线程
addWorker(null, false);
}
/*如果当前不处于running状态 或者说 当前任务队列满了
直接创建worker对象 传入任务并判断条件为maximumpoolsize
*/
else if (!addWorker(command, false))
reject(command);
}
addWorker
该方法分为两部分
第一本分for (;;)
对应的就是将当前线程池数量个数+1,不涉及线程创建
第二部分 将worker(包含当前任务(可能为null)和 新生成的线程) 存储到hashSet(记录线程池的worker)当中,启动当前worker线程执行当前任务
第二部分执行在排它锁的同步块中完成(涉及到修改成员变量)
所以addworker 就是来创建执行任务的线程的
/*
* Methods for creating, running and cleaning up after workers
*/
/**
检查是否可以根据当前池状态和给定的界限(核心或最大值)添加新的辅助进程。
如果是,则相应地调整worker计数,如果可能,将创建并启动一个新的worker,
并将firstTask作为其第一个任务运行。如果池已停止或符合关闭条件,则此方法返回false。
如果线程工厂在被询问时未能创建线程,它也会返回false。如果线程创建失败,
可能是由于线程工厂返回null,或者是由于异常(在中通常是OutOfMemoryError线程启动()),
我们干净利落地回滚。
*
* @param firstTask 新线程应该首先运行的任务(如果没有,则为null)。
* worker是用一个初始的第一个任务(在execute()方法中)创建的,
* 用于在少于corePoolSize线程(在这种情况下,我们总是启动一个线程)
* 或当队列已满(在这种情况下,我们必须绕过queue)时绕过队列。
* 最初的空闲线程通常是通过prestartCoreThread创建的,或者用来替换其他即将死亡的工人。
*
* @param core 如果为true,则使用corePoolSize作为绑定,否则使用maximumPoolSize。
* (这里使用布尔指示符而不是值,以确保在检查其他池状态后读取新值)
* @return true if successful
*/
private boolean addWorker(Runnable firstTask, boolean core) {
//标签
retry:
for (;;) {
//获取线程池的状态
int c = ctl.get();
//拿到正在运行中的状态
int rs = runStateOf(c);
// Check if queue empty only if necessary.
/*
1.第一个判断是不是该创建线程
运行状态>= SHUTDOWN状态
并且 当前运行状态不等于SHUTDOWN 当前任务不等于空 队列等于空
意思就是当前线程池的级别在shutDown以上就不应该创建线程了
*/
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
//获取线程池已有数量
int wc = workerCountOf(c);
/*
2.第二个判断 是否创建线程还是入队列
线程池已有数量>= 最大线程池数量
或者 core 判断是否corePoolSize : maximumPoolSize 进行比较
当大于指定参数 则不应该创建线程 应该入队列
*/
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
/*
第一部分代码的核心内容就是将线程池的数量+1
修改线程数量count CAS操作 成功跳出循环 第一部分执行结束
*/
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;
//线程添加标识 true 为成功
boolean workerAdded = false;
//我们待创建的线程任务 继承AQS 对排它锁进行了实现 封装了线程和任务
Worker w = null;
try {
/*new Worker(firstTask); 将当前任务传入 构造方法中
* Worker(Runnable firstTask) {
* /*
* 表示不要让线程在运行之前被中断,
* shutDown 和shutDownNow 会判断该值是不是>=0
* */
* setState(-1); // inhibit interrupts until runWorker
* this.firstTask = firstTask;
* //为成员变量赋值了一个线程实例(通过传入的线程工厂)
* this.thread = getThreadFactory().newThread(this);
* }
*/
//创建woker对象 (封装了任务和当前创建的线程)
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();
//将线程存在Hashset集合当中 ,该集合存储当前线程池所有线程worker
//类似ThreadLocal的entry数组
workers.add(w);
int s = workers.size();
//只是追踪线程池最大数量大小
if (s > largestPoolSize)
largestPoolSize = s;
//标识当前worker存储到Set集合当中
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {//当前worker成功是否成功添加
//启动线程 执行当前传进来的任务,在worker构建时任务已经传入
t.start();
workerStarted = true;//标识线程启动了
}
}
} finally {
if (! workerStarted)//是否启动线程成功
//未启动成功 直接回滚 其实是CAS 将当前第一部分线程池+1的数量 减1
//具体看如下截图
addWorkerFailed(w);
}
//返回当前线程的启动状态
return workerStarted;
}
Worker
ThreadPoolExecutor的内部类,该方法主要是用来封装当前任务和执行当前任务的线程实例该方法继承了AQS 实现了其AQS的排它锁功能
当前对象有两个成员变量是用来接收当前的任务和通过线程池参数传进来的线程工厂实现类的newThread()
生成的线程实例第三个成员变量是用来统计当前worker的执行的任务数
其次实现了runnable 本质上当通过worker的构造方法来实现worker对象时已经将当前worke对象传入当前的线程实例当中 ,
当当前线程调用start方法是执行重写的run方法
由上图我们可以看出当前的线程执行的run方法当中调用runworker方法。
runWorker
主工作循环。重复地从阻塞队列中获取任务并执行它们
/**
* Main worker run loop. Repeatedly gets tasks from queue and
* executes them, while coping with a number of issues:
*
* @param w the worker
*/
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
//获取当前worker的任务(任务可能为空20)
Runnable task = w.firstTask;
//任务的清理 防止重复执行
w.firstTask = null;
//此时线程已经运行了 允许中断线程池 具体看AQS worker中tryRelease的实现
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
/*
1.当前任务不为空时 执行提交的的任务 此时不经过阻塞队列(线程数<corePoolSize)
这个地方worker有对应任务分两种情况
1.线程数<corePoolSize
2.任务数小于MaximumPoolSize且queue满了
否则执行的任务 = getTask()
循环从阻塞队列获取的头元素
此时就开始按照顺序 先执行阻塞队列的任务
*/
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判断就是判断当前线程池是否处于stop状态
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
//执行任务前置 执行一些预先任务 当前ThreadPoolExecutor空实现
//我们可以在自定中处理一些前置操作
beforeExecute(wt, task);
Throwable thrown = null;
try {
//执行传进来的任务或者队列第一个的任务的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 {
//任务执行完毕设置为null
task = null;
//统计线程池当前线程完成的任务数
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//worker退出
processWorkerExit(w, completedAbruptly);
}
}
如下图当线程池数量<corePoolSize时 直接创建线程执行任务
当线程数量大于maximunPoolSize时先想任务放置到阻塞队列 线程在执行时任务时通过runWorker的while循环getTask()去拿到队列头结点的任务
当任务队列满了 他就会像跟之前一样将任务和线程放置到同一个worker当中 直接通过线程执行当前任务,当任务数大于MaximumPoolSize+queue.size时执行阻塞队列
由此我们可以看出 runworker就是用来执行任务,执行的任务有两部分
- 随着worker的创建附带过来的任务( 1.runworkerThread< corepoolSize 2.runworkerThread< maximumPoolSzie 和Queue 满了时 )
- 在阻塞队列拿到的头任务
shutdown 关闭线程池
启动有序shutdown,在此过程中执行以前提交的任务,但不接受任何新的任务。
/**
启动有序shutdown,在此过程中执行以前提交的任务,但不接受任何新任务。
如果已经关闭,则调用没有其他效果的(多次调用无副作用)。
此方法不等待以前提交的任务完成执行。使用awaitermination来执行此操作。
*
* @throws SecurityException {@inheritDoc}
*/
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//权限检查
checkShutdownAccess();
//将线程池的状态设置为SHUTDOWN (内部使用的是CAS 无限循环)
advanceRunState(SHUTDOWN);
//中断接收任务 对于阻塞的任务和执行的任务 继续执行
//本质上是遍历 HashSet<worker> 中的每个worker
interruptIdleWorkers();
//线程池关闭前要做的事情 jdk 是空实现
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
//调用terminated方法关闭线程池将线程池状态设置为 TERMINATED 底层调用singnalAll
//唤醒所有当前等待线程(一些因为异常阻塞的线程)并销毁
tryTerminate();
}
shutdownNow关闭线程池
尝试停止所有正在执行的任务,停止处理等待的任务,并返回等待执行的任务列表。从该方法返回时,这些任务将从任务队列中排出(移除)。
跟shutdown有很大的不同他除了接受任务之外还会丢弃阻塞队列的任务并且返回给调用者。同时终止执行的任务
/**
*/
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
//线程池状态修改为STOP
advanceRunState(STOP);
//中断接收任务同时中断正在执行的任务
interruptWorkers();
//将阻塞队列的任务放置到tasks当中同时销毁阻塞队列的任务
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}