java
中线程池的核心实现是ThreadPoolExecutor
,那么这个对象是如何实现的呢?在分析前一般最大的疑问是:
-
1、线程池的中线程对象如何被复用的?
猜测:线程池的中线程对象的的run方法应该是一个死循环,这样才能保证运行完一个任务后,线程对象不会被关闭
-
2、线程池如何优雅的替换的线程要执行的任务?
猜测:应该是通过某种方法不停的获取不同的任务对象(
Runnable
),然后线程对象调用该runnable
实例的run()方法
一、ThreadPoolExecutor
结构分析
UML
图
这里我们采用自顶向下的模式来分析下整个类的功能。首先最顶层的接口为Executor
,其基本的定义如下:
public interface Executor {
/**
* Executes the given command at some time in the future. The command
* may execute in a new thread, in a pooled thread, or in the calling
* thread, at the discretion of the {@code Executor} implementation.
*
* @param command the runnable task
* @throws RejectedExecutionException if this task cannot be
* accepted for execution
* @throws NullPointerException if command is null
*/
void execute(Runnable command);
}
只定义了一个execute的方法,用来执行一个新的线程任务。
java.util.concurrent.ExecutorService
接口的方法定义:
ExecutorService
接口新增了关闭线程池的方法(shutdown)、执行带返回结果的线程任务方法(submit)。
AbstractExecutorService
类解析:
该类中主要实现了submit方法。
二、ThreadPoolExecutor
使用示例
1、ThreadPoolExecutor
可以通过全参数的构造函数进行实例化:
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
:线程池拒绝执行任务时候的处理器。
Java 线程池框架提供了以下4种策略
AbortPolicy
:直接抛出异常。CallerRunsPolicy
:只用调用者所在线程来运行任务。DiscardOldestPolicy
:丢弃队列里最近的一个任务,并执行当前任务。DiscardPolicy
:不处理,丢弃掉。
2、使用示例
public class ThreadPoolDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//
ExecutorService executorService = new ThreadPoolExecutor(1, 1, 10,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(16), Executors.defaultThreadFactory());
executorService.execute(()->{
System.out.println("12312432");
});
Future<String> submit = executorService.submit(() -> {
Thread.sleep(5000);
return "11";
});
//阻塞获取异步执行的结果
String result = submit.get();
System.out.println(result);
//关闭线程池
executorService.shutdown();
}
}
三、ThreadPoolExecutor
源码分析
1、ThreadPoolExecutor
执行的任务的示意图如下:
1、如果我们运行的线程数少于corePoolSize
,则创建新线程。
2、如果运行的线程等于或者多于corePoolSize
,则将任务添加到阻塞队列中(BlockingQueue
)。
3、如果无法将任务放在阻塞队列中,那么就创建额外的线程来执行任务。额外的线程在经历了最大的空闲时间后,将会被销毁。
4、如果创建新的线程池将使用当前运行的线程超出maximumPoolSize
,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()
方法。
ThreadPoolExecutor
采取上述的总体设计思路,是为了在执行execute()方法的时候,尽可能的避免获取全局锁(创建新的线程的时候,需要获取全局锁)。在ThreadPoolExecutor
完成预热后,几乎所有的executor()方法调用都是执行步骤2,而步骤2不需要获取全局锁。
2、execute()方法执行
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();
//查看当前的线程是否小于核心线程数,
//小于则新增一个work线程,并将当前命令作为该线程的第一个任务
//运行的是上图的第一个分支,创建线程的时候需要全局锁
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);
}
//此时说明运行线程数已经大于核心线程数,且工作队列中已经放满了
//运行的是分支3
else if (!addWorker(command, false))
//没有添加成功,则运行分支4
reject(command);
3、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.
//只有在调用shutdown方法采用可能进入
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
//如果是当前运行的线程数大于容量或者
//核心线程数(创建的是核心线程取corePoolSize、创建的是非核心线程数,则取maximumPoolSize)
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//尝试对运行的线程数+1,如果成功,则跳出这两层循环
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对象
w = new Worker(firstTask);
//获取worker对象的线程对象
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();
//经过上面的检测,后将当前woker对象添加到集合中。
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
// 设置成功添加的标识
workerAdded = true;
}
} finally {
//释放全局锁
mainLock.unlock();
}
if (workerAdded) {
//启用当前的线程
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
通过源码及注解我们可以看出,当我们新增一个工程线程(woker
实例的时候),我们会经历如下主要步骤:
- 1、使用运行的线程 (
wc
)< 允许的最大线程数(如果新增核心线程则是最大核心线程数),则添加失败 - 2、不满足条件1,则会新建使用当前的任务(
Runnable实例
)来新建一个woker
实例。 - 3、获取当前的
woker
中的线程对象,启用该线程。
从上面的分析,我们可以看出,worker对象是线程池实现的底层核心对象。接下来我们将重点分析该对象
4、Woker
对象分析
Woker
对象的部分源码:
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;
//通过初始化ThreadPoolExecutor对象时传入的线程工厂对象来创建一个新的线程对象
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
//woker对象的绑定的线程启动的时候,将会调用runWorker方法。猜测该方法会死循环运行某些方法
public void run() {
runWorker(this);
}
...
}
从源码可以看出,woker
是Runnable
接口的一个实现类。 其绑定的线程对象使用该worker作为任务对象进行初始化的。所以在后续只要我们调用woker
绑定的线程对象的话,就会调用自身实现的run()方法。分析到这里,我们发现runwoker()
方法是 Woker
对象的实现任务调用的核心方法。这个对象源码实现回答了开篇的第一个问题。
5、runwoker(Woker woker)
方法分析
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//第一个任务存在,或者能够从阻塞的任务队列种获取到任务的时候
//如果当前的运行的线程数大于核心线程数,超过超时时间后将会返回null。那线程也将会结束
//这里回答了开篇的第二个问题
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();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
//任务执行后的方法... beforeExecute/afterExecute是两个为子类预留的钩子方法
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//调用shutdown方法后,如果还有那么就进行处理
processWorkerExit(w, completedAbruptly);
}
}
从上面的源码及注释可以看出,代码的逻辑也相对比较好理解。主要第一次任务或者能够从阻塞的任务队列种获取到任务对象,那么该方法将会被一直运行。里面主要完成的任务是:
- 1、调用钩子方法
beforeExecute
- 2、真正执行任务
task.run();
- 3、调用钩子方法
afterExecute
getTask()
对象是该方法种获取任务的核心方法,接下来我们分析它。
6、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;
}
}
}
7、submit()方法解析
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
//1、新建了一个RunnableFuture实例 ---> FutureTask
RunnableFuture<T> ftask = newTaskFor(task);
//2、调用第二步种的execute方法
execute(ftask);
return ftask;
}
从上述的源码我们可以清楚的看出。submit方法实际上就是将Callable对象封装成一个RunnableFuture
对象。那么这里我们可以肯定的是RunnableFuture
的实例一定是一个Ruannable
(因为执行了execute(ftask)
)接口,还实现了Future
(该方法返回的是Future
对象)接口。查看下RunnableFuture
接口的定义
//
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
然后我们着重看下这个实例对象的run方法实现
//java.util.concurrent.FutureTask#run
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
//1、获取构造实例方法方法时传入的callable对象
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
//2、执行callable需要执行的任务
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
//如果发生异常,那么设置相关异常信息
setException(ex);
}
if (ran)
//设置返回结果信息
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
四、总结
线程是一个应用系统种比较稀缺的资源。如果我们频繁的创建/销毁将会对整个系统产生较大的影响,而线程池的出现,最大程度复用线程对象。使得我们在运行一个异步任务的时候,不需要重新去创建一个新的线程,而在使用完成后也不需立马去销毁。总的来说主要好处有:
- 1、降低资源消耗。通过复用线程达到
- 2、提升响应速度。当任务到达时,可直接执行。无需新建线程
- 3、提升线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控