目录
今天我们来说一说java编程中一个非常重要的工具:java线程池,顾名思义,线程池就是存放线程的池子。要使用线程的时候从池子里面拿出来,不用了再放回去。接下来我们将从源码的角度一步步分析java线程池的奥义。
JAVA线程池
先来看一下java线程池(ThreadPoolExecutor)大致的一个类图
可以看到ThreadPoolExecutor的父接口有Executor和ExecutorService,其中ExecutorService是对Executor接口的扩展,AbstractExecutorService是ThreadPoolExecutor父类,这个类中也实现一些非常重要的方法。需要重点分析的还是ThreadPoolExecutorService,我们通常会使用如下代码来创建java线程池
public void commit() throws InterruptedException {
ExecutorService executor = new ThreadPoolExecutor(10, 20, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(50));
executor.execute(new Runnable() {
@Override
public void run() {
// do something...
}
});
}
我们来看一下顶层接口Executor
public interface Executor {
void execute(Runnable command);
}
里面只有一个方法execute,是java线程池最核心的方法。返回类型是void,这个方法入参是一个runnable的实例,代表提交一个任务。
ExecutorService
ExecutorService是对Executor接口的扩展,这个接口中提供了更加丰富的功能。我们常用的也是这个接口。
public interface ExecutorService extends Executor {
/**
* 关闭线程池,不接受新的任务。但是这个方法并不会终止正在执行的任务
*/
void shutdown();
/**
* 也是关闭线程池,不接受新的任务。但是这个方法会停止所有正在执行的任务
*/
List<Runnable> shutdownNow();
/**
* 判断线程池是否已经被关闭
*/
boolean isShutdown();
/**
* 调用shutDown或者shutDownNow后,如果所有任务都完成了,返回true,否则返回false
*/
boolean isTerminated();
/**
* 调用这个方法会等待所有任务完成,并且指定了等待的超时时间,返回是否等待超时。
* 不论是调用shutDown还是shutDownNow,都不会等待任务结束。
* 所以我们应该是先调用shutDown/shutDownNow,再调用此方法等待所有任务完成
*/
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
/**
* 提交一个任务,不同于execute方法,这里的入参是callable,并且返回值是Future
*/
<T> Future<T> submit(Callable<T> task);
/**
* 提交一个任务,但是这里的任务是runnable,
* runnable执行run后是无返回值的,所以这里传入的result直接作为返回值。
*/
<T> Future<T> submit(Runnable task, T result);
/**
* 提交一个runnable任务
*/
Future<?> submit(Runnable task);
/**
* 执行传入的所有的任务
*/
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
/**
* 执行的传入的所有任务,并指定超时时间
*/
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
/**
* 执行传入的所有任务,只要其中一个任务完成这个方法就可以返回了,返回这个任务的执行结果
*/
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
/**
*同上一个方法,只不过这个方法指定了超时时间
*/
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
从这里我们看到,线程池提交任务的方法有execute和submit,submit和execute最大区别在于submit方法可以获取到线程的执行结果。这里涉及到一个很重要的类:FutureTask,我们下面来简单看下FutureTask的类图
我们看到FutureTask实现了RunnableFuture,而RunnableFuture继承了Runnable和Future,再来看看FutureTask类中的重要属性和构造方法
public class FutureTask<V> implements RunnableFuture<V> {
/**
* 当前任务的执行状态,有以下七种,初始状态为NEW
*/
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
/** 这个属性很重要,就是依靠它来执行任务的 */
private Callable<V> callable;
/** 调用get时,返回的result*/
private Object outcome; // non-volatile, protected by state reads/writes
/** 真正执行callable任务的线程 */
private volatile Thread runner;
......
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
// 这里会把runnable 和 result组装成一个callable对象
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
......
}
submit方法之所以可以获取线程执行结果,全依赖于callable和FutureTask。FutureTask实现了Runnable的run方法,在run方法中会调用callable.call()获取执行结果,并将执行结果就存储在FutureTask中。
调用submit方法的时候,直接将callable/runnable对象包装成FutureTask,然后调用FutureTask.run()。最终返回FutureTask对象。我们调用FutureTask.get()就可以阻塞获取到线程的执行结果了。
本文不对FutureTask做详细讲解,我们接着看AbstractExecutorService
AbstractExecutorService
AbstractExecutorService类实现了许多父类的方法,供子类使用
public abstract class AbstractExecutorService implements ExecutorService {
/**
* 将runnable和value包装成FutureTask,以便获取线程执行结果
*/
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
/**
* 将callable包装成FutureTask,以便获取线程执行结果
*/
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
/**
* 提交任务,返回Future
*/
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
// 组装FutureTask
RunnableFuture<Void> ftask = newTaskFor(task, null);
// 将任务交给子类的execute方法去执行
execute(ftask);
return ftask;
}
/**
* 提交任务,返回Future,入参跟上面的不一样
*/
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
// 组装FutureTask
RunnableFuture<T> ftask = newTaskFor(task, result);
// 将任务交给子类的execute方法去执行
execute(ftask);
return ftask;
}
/**
* 提交任务
*/
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
/**
* 将collection中的任务提交给线程池执行,只要有一个任务完成,这个方法就可以返回了
*/
private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
boolean timed, long nanos)
throws InterruptedException, ExecutionException, TimeoutException {
if (tasks == null)
throw new NullPointerException();
// 获取集合中任务的数量
int ntasks = tasks.size();
if (ntasks == 0)
// 没有任务抛出异常
throw new IllegalArgumentException();
// 用Future来包装任务
ArrayList<Future<T>> futures = new ArrayList<Future<T>>(ntasks);
// ExecutorCompletionService这个类的构造方法传入了this作为真正的Executor
// 这个方法会在每个任务完成后,把被FutureTask包装好的callable任务保存到一个LinkedBlokingQueue中
ExecutorCompletionService<T> ecs =
new ExecutorCompletionService<T>(this);
try {
// 这里可以学习一下人家的异常处理
ExecutionException ee = null;
final long deadline = timed ? System.nanoTime() + nanos : 0L;
// 迭代所有的callable任务
Iterator<? extends Callable<T>> it = tasks.iterator();
// 这里调用了ecs.submit提交一个任务给线程池,并且将封装每个任务的FutureTask保存在futures中
futures.add(ecs.submit(it.next()));
// 任务数-1
--ntasks;
// 从名字都可以看出来,这个代表正在执行的任务数
int active = 1;
for (;;) {// 注意:这里是个循环
// 我们上面说ExecutorCompletionService中有个LinkedBlockingQueue用于保存每个任务的FutureTask。
// 那么这里调用poll方法尝试将FutureTask取出来,这里的poll方法是非阻塞的
Future<T> f = ecs.poll();
// 如果f为null,说明前面提交的任务还没执行完成。
if (f == null) {
// 我们说这个方法只要有一个任务执行完成就返回了,
// 现在还没有任务执行完成,那么接着执行后面的任务
if (ntasks > 0) {
--ntasks;
futures.add(ecs.submit(it.next()));
++active;
}
// 如果这里的active为0,说明所有的任务都执行失败了,从这里break出去
else if (active == 0)
break;
// 进入这个分支说明已经没有任务了并且需要检测超时
else if (timed) {
// 再poll一次,等待nanos这么久,看有没有任务完成
f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
// 如果超时,抛出异常跳出循环
if (f == null)
throw new TimeoutException();
nanos = deadline - System.nanoTime();
}
// 到这一步说明已经没有任务了,也没有设置超时,
// 那么调用take方法阻塞,直到有任务执行完成
else
f = ecs.take();
}
// 如果f!=null说明有任务执行完成了
if (f != null) {
// 正在执行的任务数-1
--active;
try {