并发编程九、J.U.C 线程池
一、线程池基本使用
在Java 中,如果每个请求到达就创建一个新线程, 创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。如果在一个 Jvm 里创建太多的线程,可能会使系统由于过度消耗内存或切换过度而导致系统资源不足。为了解决这个问题就有了线程池的概念,线程池的核心逻辑是提前创建好若干个线程放在一个容器中。如果有任务需要处理,则将任务直接分配给线程池中的线程来执行就行,任务处理完以后这个线程不会被销毁,而是等待后续分配任务。同时通过线程池来重复管理线程还可以避免创建大量线程增加开销。
1. 线程池的优势
合理的使用线程池,可以带来一些好处
- 降低创建线程和销毁线程的性能开销
- 提高响应速度,当有新任务需要执行是不需要等待线程创建就可以立马执行
- 合理的设置线程池大小可以避免因为线程数超过硬件资源瓶颈带来的问题
2. Java中提供的线程池 Api
线程池的核心类是java.util.concurrent.ThreadPoolExecutor
,为了方便我们使用,JUC中Doug Lea
直接提供了java.util.concurrent.Executors
工厂类来创建常用的几种类型的线程池。
方法 | 说明 |
---|---|
java.util.concurrent.Executors#newFixedThreadPool | 该方法返回一个固定数量的线程池,线程数不变,当有一个任务提交时,若线程池中空闲,则立即执行,若没有,则会被暂缓在一个任务队列中,等待有空闲的线程去执行。 |
Executors#newSingleThreadExecutor | 创建一个线程的线程池,若空闲则执行,若没有空闲线程则暂缓在任务队列中。 |
Executors#newCachedThreadPool | 返回一个可根据实际情况调整线程个数的线程池,不限制最大线程数量,若有空闲的线程则执行任务,若无任务则不创建线程。并且每一个空闲线程会在60秒后自动回收。 |
Executors#newScheduledThreadPool | 创建一个可以指定线程的数量的线程池,但是这个线程池还带有延迟和周期性执行任务的功能,类似定时器。 |
可以根据这个例子直观的看出几种线程池的区别:通过输出的线程名称来看具体有多少个线程在执行
import java.util.concurrent.*;
public class MyTask implements Runnable{
public static void main(String[] args) {
ExecutorService pool;
// 一个线程的线程池
pool = Executors.newSingleThreadExecutor();
// 根据实际情况调整线程个数的线程池
pool = Executors.newCachedThreadPool();
// 带定时调度的,可以延期执行 心跳检测
pool = Executors.newScheduledThreadPool(5);
// 固定线程数
pool = Executors.newFixedThreadPool(3);
// 自定义
pool = new ThreadPoolExecutor(5, 10,1L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
/*
pool.execute(()-> { });
pool.submit(()->{ });
Future<Object> rst = pool.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
String name = "------ " + Thread.currentThread().getName();
TimeUnit.SECONDS.sleep(3);
System.out.println( name);
return name;
}
});
try {
System.out.println(rst.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
*/
for (int i = 0; i < 20; i++) {
pool.execute(new MyTask());
}
pool.shutdown();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
二、线程池源码分析
在分析源码之前,其实可以先想一下如果让我们自己来设计线程池,应该考虑哪些要素?
有几种情况可以考虑一下:
1. 首先线程池是为了减少频繁创建销毁线程的开销,所有应该会有几个可以一直存活的线程,这个数量我们自己可以来设置;
那怎么保证让线程一直存活不被销毁呢? 我们知道线程在start调用run方法执行完毕后就可以被销毁;那要想让线程不被销毁,是不是需要run方法永远不能执行完毕。
2. 我们制定了几个线程来执行任务,类似于消费者,当需要执行的任务突然蜂拥而来,这几个线程处理不及的任务,是不是需要找个地方先存储起来比如队列,等线程忙完一个再取一个来执行;
这里其实就可以看做典型的生产者消费者: 生产者来生产任务,放入队列中,几个线程作为消费者从队列中取出任务来消费
3. 有一种场景,消费者消费的速度远远来不及生产者生产的速度,队列就会存在放满了的情况,这时候再继续生产的话,新的任务需要怎么处理
4. 任务实在太多 这几个消费者线程确实来不及处理,可不可以先招募一批临时工来辅助处理
5. 签约的临时工是不是要有合同期限,到期后销毁
考虑到以上几种场景,我们来设计的话可能就需要几个参数:
线程池中可以一直存活的线程数量、存放任务的队列、任务队列满了的话新任务的处理策略、辅助处理任务的线程、辅助处理任务的线程存活时间
1. 线程池核心参数
接下来真正从入口分析线程池的实现原理:
如果我们看了Executors
创建上面几种线程池的源码,可以看到都是基于ThreadPoolExecutor
创建的,真正的参数也就是这些了:
// 线程池的核心参数
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
2. 线程池原理分析
- 首先判断线程池中的线程数是否超过核心线程数,
如果没有,则新建一个核心线程addWorker
,并将其加入线程池一个hashset
中,最后启动核心线程worker.start
- 核心线程start启动后,调用run方法自旋,内部判断firstTask是不是空,
非空的话直接运行task的run方法执行任务;
firstTask是空的话,从阻塞队列中取任务,取到则可以执行任务,取不到的话核心线程阻塞 - 当继续提交任务时,如果当前线程数已经超过核心线程数,则会将任务先放入阻塞队列中去,由核心线程去消费
- 当队列已满,会尝试创建核心线程来执行,此时会根据最大线程数量来判断能否继续创建核心线程
- 当超过最大线程数时,执行拒绝策略
具体原理我们可以从执行任务的java.util.concurrent.ThreadPoolExecutor#execute
方法为入口:
java.util.concurrent.ThreadPoolExecutor#execute
private static int workerCountOf(int c) { return c & CAPACITY; }
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) { //1.当前池中线程比核心数少,新建一个线程执行任务
if (addWorker(command, true)) // addWorker 创建核心线程
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) { //2.核心池已满,但任务队列未满,添加到队列中
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command)) // 任务成功添加到队列以后,再次检查是否需要添加新的线程,因为已存在的线程可能被销毁了
reject(command); // 如果线程池处于非运行状态,并且把当前的任务从任务队列中移除成功,则拒绝该任务
else if (workerCountOf(recheck) == 0) // 如果之前的线程已被销毁完,新建一个线程
addWorker(null, false);
}
else if (!addWorker(command, false)) // 3. 核心池已满,队列已满,试着创建一个新线程
reject(command); // 如果创建新线程失败了,说明线程池被关闭或者线程池完全满了,执行拒绝策略
}
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
创建核心线程addWorker
如果工作线程数小于核心线程数的话,会调用addWorker ,顾名思义,其实就是要创建一个工作线程。我们来看看源码的实现:
先只挑重点的来看,不至于陷入细节不可自拔
大致可以分为:
A. 创建核心线程worker; B. 将核心线程放入线程池; C. 启动核心线程
java.util.concurrent.ThreadPoolExecutor#addWorker
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); // ctl,原子类,主要是用来保存线程数量和线程池的状态。其中高3位来保存运行状态,低29位来保存线程数量。
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;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
private final HashSet<Worker> workers = new HashSet<Worker>(); // 线程池
// A. addWorker 创建核心线程
private boolean addWorker(Runnable firstTask, boolean core) { // 入参: 需要执行的任务,是否核心线程
retry: //goto 语句,避免死循环
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
//如果线程处于非运行状态,并且 rs 不等于 SHUTDOWN 且 firstTask 不等于空且workQueue 为空,直接返回 false (表示不可添加 work 状态)
if (rs >= SHUTDOWN && // 1. 线程池已经 shutdown 后,还要添加新的任务,拒绝
! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) // 2. (第二个判断) SHUTDOWN 状态不接受新任务,但仍然会执行已经加入任务队列的任务,所以当进入 SHUTDOWN 状态,而传进来的任务为空,并且任务队列不为空的时候,是允许添加新线程的 , 如果把这个条件取反,就表示不允许添加 worker
return false;
for (;;) { // 自旋
int wc = workerCountOf(c); //获得 Worker 工作线程数
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) ////如果工作线程数大于默认容量大小或者大于核心线程数大小,则直接返回 false 表示不能再添加 worker。
return false;
if (compareAndIncrementWorkerCount(c)) // //通过 cas 来增加工作线程数,如果 cas 失败,进入下次自旋
break retry;
c = ctl.get(); // Re-read ctl // 再次获取 ctl 的值
if (runStateOf(c) != rs) //这里如果不相等,说明线程的状态发生了变化,继续重试
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
//上面这段代码主要是对 worker 数量做原子+1 操作,下面的逻辑才会正式构建一个 worker
boolean workerStarted = false; //工作线程是否启动的标识
boolean workerAdded = false; //工作线程是否已经添加成功的标识
Worker w = null;
try {
w = new Worker(firstTask); //A: A01. 创建一个核心线程 -> Worker类内 A02-A04
final Thread t = w.thread; // A05. w.thread,其实就是刚创建的worker本身
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)) { //只有当前线程池是正在运行状态,[或是 SHUTDOWN 且 firstTask 为空],才能添加到 workers 集合中
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException(); //任务刚封装到 work 里面,还没 start,你封装的线程就是 alive,几个意思?肯定是要抛异常出去的
workers.add(w); //B: B01. 放入线程池
int s = workers.size();
if (s > largestPoolSize) // 如果集合中的工作线程数大于最大线程数,这个最大线程数表示线程池曾经出现过的最大线程数
largestPoolSize = s; // 更新线程池出现过的最大线程数
workerAdded = true; // 表示工作线程创建成功了
}
} finally {
mainLock.unlock(); // 解锁
}
if (workerAdded) {
t.start(); //C: C01. 线程启动 -> 其实就是启动worker线程,也就是调用了 worker的run方法 -> 见 Worker类的run()方法 C02 ->C05
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w); //如果添加失败,就需要做一件事,就是递减实际工作线程数(还记得我们最开始的时候增加了工作线程数吗)
}
return workerStarted;
}
Worker类
我们发现 addWorker 方法只是构造了一个 Worker,并且把 firstTask 封装到 worker 中,它是做什么的呢?我们来看看
- 每个 worker,都是一条线程,同时里面包含了一个 firstTask,即初始化时要被首先执行的任务.
- 最终执行任务的,是 runWorker()方法
Worker 类继承了 AQS,并实现了 Runnable 接口,注意其中的 firstTask 和 thread 属性:firstTask 用它来保存传入的任务;thread 是在调用构造方法时通过 ThreadFactory 来创建的线程,是用来处理任务的线程。
在调用构造方法时,需要传入任务,这里通过 getThreadFactory().newThread(this);来新建一个线程,newThread 方法传入的参数是 this,因为 Worker 本身继承了 Runnable 接口,也就是一个线程,所以一个 Worker 对象在启动的时候会调用 Worker 类中的 run 方法。
Worker 继承了 AQS,使用 AQS 来实现独占锁的功能。为什么不使用 ReentrantLock 来实现呢?可以看到 tryAcquire 方法,它是不允许重入的,而 ReentrantLock 是允许重入的:
lock 方法一旦获取了独占锁,表示当前线程正在执行任务中;那么它会有以下几个作用 - 如果正在执行任务,则不应该中断线程;
- 如果该线程现在不是独占锁的状态,也就是空闲的状态,说明它没有在处理任务,这时可以对该线程进行中断;
- 线程池在执行 shutdown 方法或 tryTerminate 方法时会调用 interruptIdleWorkers 方法来中断空闲的线程,interruptIdleWorkers 方法会使用 tryLock 方法来判断线程池中的线程是否是空闲状态
- 之所以设置为不可重入,是因为我们不希望任务在调用像 setCorePoolSize 这样的线程池控制方法时重新获取锁,这样会中断正在运行的线程
private final class Worker // 核心线程Worker 继承了 AQS,实现了Runnable,表明它也是一个线程,且有锁的功能
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; // 核心线程,也是真正执行 task 的线程,从构造函数可知是由ThreadFactury 创建的
/** Initial task to run. Possibly null. */
Runnable firstTask; // 这就是需要执行的 task
/** 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) { // A02. 创建核心线程构造方法
setState(-1); // inhibit interrupts until runWorker // 初始状态 -1, 防止在调用 runWorker() ,也就是真正执行 task前中断 thread
this.firstTask = firstTask; // A03. 赋值需要执行的任务 firstTask
this.thread = getThreadFactory().newThread(this); // A04. thread成员变量其实就是worker本身
}
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this); // C02. run方法调用的是 ThreadPoolExecutor#runWorker
}
...
...
}
runWorker 方法
前面已经了解了 ThreadPoolExecutor 的核心方法 addWorker,主要作用是增加工作线程,而 Worker 简单理解其实就是一个线程,里面重新了 run 方法,这块是线程池中执行任务的真正处理逻辑,也就是 runWorker 方法,这个方法主要做几件事
- 如果 task 不为空,则开始执行 task
- 如果 task 为空,则通过 getTask()再去取任务,并赋值给 task,如果取到的 Runnable 不为空,则执行该任务
- 执行完毕后,通过 while 循环继续 getTask()取任务
- 如果 getTask()取到的任务依然是空,那么整个 runWorker()方法执行完毕
java.util.concurrent.ThreadPoolExecutor#runWorker
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask; // C03. 得到需要执行的任务
w.firstTask = null;
w.unlock(); // allow interrupts. unlock,表示当前 worker 线程允许中断,因为 new Worker 默认的 state=-1,此处是调用Worker 类的 tryRelease()方法,将 state 置为 0,而 interruptIfStarted()中只有 state>=0 才允许调用中断
boolean completedAbruptly = true;
try {
//通过这个 while 循环实现 [线程复用]
while (task != null || (task = getTask()) != null) { // C04. 如果 task 为空,则通过getTask 来获取任务
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); // 执行前, 这里默认是没有实现的,在一些特定的场景中我们可以自己继承 ThreadpoolExecutor 自己重写
Throwable thrown = null;
try {
task.run(); // C05. 调用task.run执行任务,不是task.start
} 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); // 执行后, 这里默认是没有实现的,在一些特定的场景中我们可以自己继承 ThreadpoolExecutor 自己重写
}
} finally {
// 置空任务(这样下次循环开始时,task依然为null,需要再通过getTask取) + 记录该 Worker 完成任务数量 + 解锁
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//1.将入参 worker 从数组 workers 里删除掉;
//2.根据布尔值 allowCoreThreadTimeOut 来决定是否补充新的 Worker 进数组workers
processWorkerExit(w, completedAbruptly);
}
}
getTask方法
worker 线程会从阻塞队列中获取需要执行的任务,这个方法不是简单的 take 数据,我们来分析下他的源码实现
你也许好奇是怎样判断线程有多久没有活动了,是不是以为线程池会启动一个监控线程,专门监控哪个线程正在偷懒?想太多,其实只是在线程从工作队列 poll 任务时,加上了超时限制,如果线程在 keepAliveTime 的时间内 poll 不到任务,那我就认为这条线程没事做,可以干掉了。
看下面的源码就会清楚了:
// C04. 从阻塞队列内取任务
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? // timed 变量用于判断是否需要进行超时控制。
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; // 判断是否需要进行超时控制;对于超过核心线程数量的这些线程,需要进行超时控制 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) : // 通过阻塞队列 poll 方法进行超时控制:带有超时时间的取数据方法,如果超时未取到任务则返回null,
workQueue.take(); // 从阻塞队列中取任务,没有则阻塞
if (r != null)
return r; // 返回取得的任务,自旋结束
timedOut = true; // 如果 r==null,说明已经超时了,设置 timedOut=true,在下次自旋的时候进行回收
} catch (InterruptedException retry) {
timedOut = false; // 如果获取任务时当前线程发生了中断,则设置 timedOut 为false 并返回循环重试
}
}
}
execute后续逻辑分析
如果核心线程数已满,说明这个时候不能再创建核心线程了,于是走第二个判断,
第二个判断逻辑比较简单,如果线程池处于运行状态并且任务队列没有满,则将任务添加到队列中
第三个判断,核心线程数满了,队列也满了,那么这个时候创建新的线程也就是(非核心线程)
如果非核心线程数也达到了最大线程数大小,则直接拒绝任务
public void execute(Runnable command) {
...
...
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) { //1.当前池中线程比核心数少,新建一个线程执行任务
if (addWorker(command, true)) // addWorker 创建核心线程
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) { //2.核心池已满,但任务队列未满,添加到队列中
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command)) // 任务成功添加到队列以后,再次检查是否需要添加新的线程,因为已存在的线程可能被销毁了
reject(command); // 如果线程池处于非运行状态,并且把当前的任务从任务队列中移除成功,则拒绝该任务
else if (workerCountOf(recheck) == 0) // 如果之前的线程已被销毁完,新建一个线程
addWorker(null, false);
}
else if (!addWorker(command, false)) // 3. 核心池已满,队列已满,试着创建一个新线程
reject(command); // 如果创建新线程失败了,说明线程池被关闭或者线程池完全满了,执行拒绝策略
}
拒绝策略
1、AbortPolicy:直接抛出异常,默认策略;
2、CallerRunsPolicy:用调用者所在的线程来执行任务;
3、DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
4、DiscardPolicy:直接丢弃任务;
当然也可以根据应用场景实现 RejectedExecutionHandler 接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务
3. Callable/Future原理分析
Callable/Future 和 Thread 之类的线程构建最大的区别在于,能够很方便的获取线程执行完以后的结果。
先看一个例子
import java.util.concurrent.*;
public class CallableTask implements Callable<String> {
@Override
public String call() throws Exception {
TimeUnit.SECONDS.sleep(5);
return "HelloWorld!";
}
public static void main(String[] args) {
Callable task = new CallableTask();
FutureTask futureTask = new FutureTask(task);
new Thread(futureTask, "t1").start();
try {
// System.out.println(futureTask.get(1L, TimeUnit.SECONDS));
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
想一想我们为什么需要使用回调呢?那是因为结果值是由另一线程计算的,当前线程是不知道结果值什么时候计算完成,所以它传递一个回调接口给计算线程,当计算完成时,调用这个回调接口,回传结果值。
这个在很多地方有用到,比如 Dubbo 的异步调用,比如消息中间件的异步通信等等…利用 FutureTask、Callable、Thread 对耗时任务(如查询数据库)做预处理,在需要计算结果之前就启动计算。
所以我们来看一下 Future/Callable 是如何实现的
Callable、Future、FutureTask 结构
上面例子中主要使用了一个Callable接口和FutureTask类,先看一下API:
Callable接口,内部只有一个call方法。
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
FutureTask类实现了RunnableFuture接口,RunnableFuture接口继承于Runnable,Future接口。
Runnable接口的实现代表了一个线程;
Future表示一个任务的生命周期,并提供了相应的方法来判断是否已经完成或取消,以及获取任务的结果和取消任务等。
public class FutureTask<V> implements RunnableFuture<V> {
...
...
...
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
public interface Runnable {
public abstract void run();
}
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
// 是否被取消
boolean isCancelled();
// 当前Future是否已结束。包括运行完成、抛出异常以及取消,都表示当前Future已结束
boolean isDone();
// 获取Future的结果值。如果当前Future还没有结束,那么当前线程就等待,
// 直到Future运行结束,那么会唤醒等待结果值的线程的。
V get() throws InterruptedException, ExecutionException;
// 获取Future的结果值。与get()相比较多了允许设置超时时间
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
分析到这里我们其实有一些初步的头绪了,FutureTask 是 Runnable 和 Future 的结合,如果我们把 Runnable 比作是生产者, Future 比作是消费者,那么 FutureTask 是被这两者共享的,生产者运行 run 方法计算结果,消费者通过 get 方法获取结果。作为生产者消费者模式,有一个很重要的机制,就是如果生产者数据还没准备的时候,消费者会被阻塞。当生产者数据准备好了以后会唤醒消费者继续执行。这个有点像我们上次可分析的阻塞队列,那么在 FutureTask 里面是基于什么方式实现的呢?
FutureTask源码分析
还以上面demo为例,主要会有几个步骤:
a、首先main方法内调用FutureTask构造方法创建futureTask
b、创建t1线程,启动来运行call方法并休眠5s
c、主线程内futureTask.get()来获取t1线程的执行结果数据,因为t1线程在休眠,此时主线程阻塞
d、t1线程休眠结束,返回结果并唤醒等待的主线程
e、 主线程拿到t1线程结果,输出
a. 首先调用FutureTask构造方法创建futureTask
public static void main(String[] args) {
Callable task = new CallableTask();
FutureTask futureTask = new FutureTask(task); // A01.创建futureTask
...
...
}
java.util.concurrent.FutureTask#FutureTask(java.util.concurrent.Callable<V>)
private Callable<V> callable;
/**
* Possible state transitions:
* NEW -> COMPLETING -> NORMAL
* NEW -> COMPLETING -> EXCEPTIONAL
* NEW -> CANCELLED
* NEW -> INTERRUPTING -> INTERRUPTED
*/
private volatile int state;
private static final int NEW = 0; // NEW 新建状态,表示这个FutureTask还没有开始运行
private static final int COMPLETING = 1; // COMPLETING 完成状态, 表示FutureTask任务已经计算完毕了,但是还有一些后续操作,例如唤醒等待线程操作,还没有完成
private static final int NORMAL = 2; // FutureTask任务完结,正常完成,没有发生异常
private static final int EXCEPTIONAL = 3; // FutureTask任务完结,因为发生异常。
private static final int CANCELLED = 4; // FutureTask任务完结,因为取消任务
private static final int INTERRUPTING = 5; // FutureTask任务完结,也是取消任务,不过发起了中断运行任务线程的中断请求
private static final int INTERRUPTED = 6; // FutureTask任务完结,也是取消任务,已经完成了中断运行任务线程的中断请求
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable; // A02. 成员变量 callable赋值
this.state = NEW; // A03. state赋初始值 NEW
}
从state的0-6中状态可以看出,只要 state>COMPLETING,就表示任务执行完毕
b、创建t1线程,启动来运行call方法并休眠5s
public static void main(String[] args) {
Callable task = new CallableTask();
FutureTask futureTask = new FutureTask(task);
new Thread(futureTask, "t1").start(); // B01. t1线程启动,调用futureTask的run方法
...
...
}
java.util.concurrent.FutureTask#run
private volatile Thread runner;
private static final sun.misc.Unsafe UNSAFE;
private static final long stateOffset;
private static final long runnerOffset;
private static final long waitersOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = FutureTask.class;
stateOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("state"));
runnerOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("runner"));
waitersOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("waiters"));
} catch (Exception e) {
throw new Error(e);
}
}
public void run() {
if (state != NEW || // B02. 判断当前状态是否是NEW,如果不是则可能是另外的线程已经操作了,不能重复执行,直接return
!UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) // B03. CAS判读当前运行线程是否是当前线程,也是为了保证只有一个线程在执行
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call(); // B04. 调用我们自己创建的demo的call方法,开始休眠5秒
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);
}
}
其实 run 方法作用非常简单,就是调用 callable 的 call 方法返回结果值 result ,根据是否发生异常,调用 set(result) 或 setEx ception(ex) 方法表示 FutureTask 任务完结。不过因为 FutureTask 任务都是在多线程环境中使用,所以要注意并发冲突问题。注意在 run 方法中,我们没有使用 synchronized 代码块或者 Lock 来解决并发问题,而是使用了 CAS 这个乐观锁来实现并发安全,保证只有一个线程能运行 FutureTask 任务。
c、主线程内futureTask.get()来获取t1线程的执行结果数据,因为t1线程在休眠,此时主线程阻塞并放入Wait等待队列
public static void main(String[] args) {
Callable task = new CallableTask();
FutureTask futureTask = new FutureTask(task);
new Thread(futureTask, "t1").start();
try {
// System.out.println(futureTask.get(1L, TimeUnit.SECONDS));
System.out.println(futureTask.get()); // C01. 主线程内获取futureTask的运行结果
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
java.util.concurrent.FutureTask#get()
/**
* Possible state transitions:
* NEW -> COMPLETING -> NORMAL
* NEW -> COMPLETING -> EXCEPTIONAL
* NEW -> CANCELLED
* NEW -> INTERRUPTING -> INTERRUPTED
*/
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;
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING) // C02. 先判断state状态,是否已完成,此时state为NEW 小于 COMPLETING
s = awaitDone(false, 0L); // C03. 阻塞线程
return report(s);
}
java.util.concurrent.FutureTask#awaitDone
private volatile WaitNode waiters;
// C03. 阻塞主线程
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) { // C04.自旋 // C09.第二次自旋 // C12. 第三次自旋
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
if (s > COMPLETING) { // 此时t1线程未执行结束,state为NEW,false
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // 此时t1线程未执行结束,state为NEW,false
Thread.yield();
else if (q == null) // C06. q为NUll
q = new WaitNode(); // C07. 创建一个WaitNode -> 至C08, 当C08执行完毕,本次自旋结束,进入下一次自旋
else if (!queued) // C10. 是否入队,此时还没有入队,进入
// C11. CAS操作入队,分为三步:
// 1. q.next = waiters,当前q入队,放入队列头结点; 2. CAS操作,更新队列waiters; 3. CAS入队成功,queued改为true;
// 之后本次自旋结束,进入下次自旋
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this); // C13. 第三次自旋,park挂起当前线程即主线程,此时主线程已封装为WaitNode存储于 waiters 单向链表内
}
}
java.util.concurrent.FutureTask.WaitNode
static final class WaitNode {
volatile Thread thread; // 保存线程
volatile WaitNode next; // 单向链表,记录下一个节点
WaitNode() { thread = Thread.currentThread(); } // C08. 构造方法创建waitNode,存储当前线程于thread属性
}
get方法就是阻塞获取线程执行结果,这里主要做了两个事情
- 判断当前的状态,如果 状态小于等于 COMPLETING ,表示 FutureTask 任务还没有完结所以调用 awaitDone 方法,让当前线程等待。
- report 返回结果值或者抛出异常
d、t1线程休眠结束,返回结果并唤醒等待的主线程
// 继续t1线程中FutureTask的run方法,休眠结束
java.util.concurrent.FutureTask#run
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call(); // D01. t1线程5s休眠结束,返回我们自己的返回值
ran = true; // D02. ran成功
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result); // D03. 设置结果
}
} 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);
}
}
java.util.concurrent.FutureTask#set
private Object outcome;
// D03. 设置结果
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) { // D04. CAS先将state改为COMPLETING状态,表示结果计算完毕
outcome = v; // D05. 将运行结果设置给成员变量 outcome
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state // D06. 将state改为NORMAL状态,真正的运行成功结束标识
finishCompletion(); // D07. t1线程执行完毕,唤醒等待t1运行结果的线程
}
}
java.util.concurrent.FutureTask#finishCompletion
// D07. t1线程执行完毕,唤醒等待t1运行结果的线程
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) { // D08. 遍历单向链表 waiters
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) { // D09. cas重置取到的节点
for (;;) { // D10. 自旋
Thread t = q.thread; // D11. 去到节点对应线程,此时即主线程
if (t != null) {
q.thread = null;
LockSupport.unpark(t); // D12. unpark唤醒线程,此时主线程从之前park位置唤醒,执行主线程的逻辑
}
WaitNode next = q.next; // D13. 判断单向链表是否有下一节点,如果存在下一节点继续唤醒
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
done();
callable = null; // to reduce footprint
}
e、 主线程被唤醒,拿到t1线程结果,输出
// 从主线程之前park被挂起出开始唤醒,执行
java.util.concurrent.FutureTask#awaitDone
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) { // E02. 唤醒之后继续自旋
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
if (s > COMPLETING) { // E03. 此时t1线程执行完毕,state状态重置为 NORMAL 大于 COMPLETING
if (q != null)
q.thread = null;
return s; // E04. 返回state,方法执行完毕
}
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
else if (q == null)
q = new WaitNode();
else if (!queued)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this); //E01. 主线程被唤醒,开始下次自旋(此时即从C13挂起步骤继续执行)
}
}
java.util.concurrent.FutureTask#get()
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L); // E05. 方法执行完毕,返回state
return report(s); // E06. 返回计算结果
}
java.util.concurrent.FutureTask#report
private Object outcome;
// E06. 返回计算结果
private V report(int s) throws ExecutionException {
Object x = outcome; // E07. t1线程的执行结果
if (s == NORMAL)
return (V)x; // E08. 返回执行结果
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
public static void main(String[] args) {
Callable task = new CallableTask();
FutureTask futureTask = new FutureTask(task);
new Thread(futureTask, "t1").start();
try {
// System.out.println(futureTask.get(1L, TimeUnit.SECONDS));
System.out.println(futureTask.get()); // 最终,执行main方法输出结果
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
4. 线程池对于Future、Callable的执行
线程池使用submit方法来执行线程,获取返回结果,那么查看submit方法就能清除其执行原理了。
import java.util.concurrent.*;
public class CallableTask implements Callable<String> {
@Override
public String call() throws Exception {
TimeUnit.SECONDS.sleep(5);
return "HelloWorld!";
}
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(3);
Future future = pool.submit(new CallableTask());
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
pool.shutdown();
}
}
a. 首先、创建了一个固定线程数的线程池:
Executors.newFixedThreadPool(3);
java.util.concurrent.Executors#newFixedThreadPool(int)
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
本质也就是创建了一个ThreadPoolExecutor对象,而ThreadPoolExecutor是继承于java.util.concurrent.AbstractExecutorService
类。
b. 线程池submit执行任务
执行pool.submit(new CallableTask())
时,其实是调用了java.util.concurrent.AbstractExecutorService#submit(java.util.concurrent.Callable<T>)
方法“。
java.util.concurrent.AbstractExecutorService#submit(java.util.concurrent.Callable<T>)
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task); // 1. newTaskFor
execute(ftask); // 线程池 执行任务,内部创建了一个核心线程worker对象来异步执行,其实内部是仍是执行 futureTask.run方法,和上面的源码分析一致
return ftask; // 返回futureTask,异步
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable); // 2. 内部其实是封装为 FutureTask 对象
}
当调用抽象类中的 submit 方法,这里其实相对于 execute 方法来说,只多做了一步操作,就是封装了一个 RunnableFuture;
然后调用execute 方法,这里面的逻辑前面分析过了,会通过 worker 线程来调用过 ftask 的run 方法。而这个 ftask 其实就是 FutureTask 里面最终实现的逻辑。
c. 获取线程执行结果System.out.println(future.get());
看到这里,其实就可以明白,线程池中获取线程执行结果也是基于java.util.concurrent.FutureTask
来实现的。
FutureTask#get()
将等待线程执行结果的线程封装为WaitNode的一个单向链表的等待队列,并将等待线程挂起;
之后在FutureTask#run
内调用Callable.run
方法执行任务,获取执行结果,然后将之前的等待线程从等待队列中唤醒来返回结果;
三、线程池的配置、自定义监控
1. 如何合理配置线程池的大小
- 需要分析线程池执行的任务的特性: CPU 密集型还是 IO 密集型
- 每个任务执行的平均时长大概是多少,这个任务的执行时长可能还跟任务处理逻辑是否涉及到网络传输以及底层系统资源依赖有关系
如果是 CPU 密集型,主要是执行计算任务,响应时间很快,cpu 一直在运行,这种任务cpu的利用率很高,那么线程数的配置应该根据 CPU 核心数来决定,CPU 核心数=最大同时执行线程数,加入 CPU 核心数为 4,那么服务器最多能同时执行 4 个线程。过多的线程会导致上下文切换反而使得效率降低。那线程池的最大线程数可以配置为 cpu 核心数+1;
如果是 IO 密集型,主要是进行 IO 操作,执行 IO 操作的时间较长,这是 cpu 出于空闲状态,导致 cpu 的利用率不高,这种情况下可以增加线程池的大小。这种情况下可以结合线程的等待时长来做判断,等待时间越高,那么线程数也相对越多。一般可以配置 cpu 核心数的 2 倍。
一个公式:线程池设定最佳线程数目 = ((线程池设定的线程等待时间+线程 CPU 时间)/ 线程 CPU 时间 )* CPU 数目
这个公式的线程 cpu 时间是预估的程序单个线程在 cpu 上运行的时间(通常使用 loadrunner测试大量运行次数求出平均值)
2. 线程池中的线程初始化
默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
prestartCoreThread():初始化一个核心线程;
java.util.concurrent.ThreadPoolExecutor#prestartCoreThread
public boolean prestartCoreThread() {
return workerCountOf(ctl.get()) < corePoolSize &&
addWorker(null, true);
}
prestartAllCoreThreads():初始化所有核心线程
java.util.concurrent.ThreadPoolExecutor#prestartAllCoreThreads
public int prestartAllCoreThreads() {
int n = 0;
while (addWorker(null, true))
++n;
return n;
}
ThreadPoolExecutor tpe=(ThreadPoolExecutor)service; tpe.prestartAllCoreThreads();
3. 线程池的关闭
ThreadPoolExecutor 提供了两个方法,用于线程池的关闭,分别是 shutdown()和 shutdownNow(),其中:
shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
4. 线程池容量的动态调整
ThreadPoolExecutor 提供了动态调整线程池容量大小的方法: setCorePoolSize()和 setMaximumPoolSize(),setCorePoolSize:设置核心池大小 setMaximumPoolSize:设置线程池最大能创建的线程数目大小
任务缓存队列及排队策略
在前面我们多次提到了任务缓存队列,即 workQueue,它用来存放等待执行的任务。workQueue 的类型为 BlockingQueue,通常可以取下面三种类型:
- ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
- LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为 Integer.MAX_VALUE;
- SynchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
5. 线程池的监控
如果在项目中大规模的使用了线程池,那么必须要有一套监控体系,来指导当前线程池的状态,当出现问题的时候可以快速定位到问题。而线程池提供了相应的扩展方法,我们通过重写线程池的 beforeExecute、afterExecute 和 shutdown 等方式就可以实现对线程的监控。
简单演示一个案例:
import java.util.Date;
import java.util.concurrent.*;
public class ShenThreadPool extends ThreadPoolExecutor {
private ConcurrentHashMap<String, Date> startTimes;
public ShenThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
this.startTimes = new ConcurrentHashMap<>();
}
public ShenThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
this.startTimes = new ConcurrentHashMap<>();
}
public static ExecutorService newCachedThreadPool() {
return new ShenThreadPool(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}
/**
* 线程池创建后初始化一个核心线程
* @return
*/
@Override
public boolean prestartCoreThread() {
return super.prestartCoreThread();
}
/**
* 线程池创建后初始化全部的核心线程
* @return
*/
@Override
public int prestartAllCoreThreads() {
return super.prestartAllCoreThreads();
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
// 记录线程执行开始时间
startTimes.put(String.valueOf(r.hashCode()), new Date());
super.beforeExecute(t, r);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
// 记录线程执行结束时间
Date start = startTimes.remove(String.valueOf(r.hashCode()));
Date end = new Date();
long used = end.getTime() - start.getTime();
System.out.println("任务耗时(ms):" + used + "\n");
// System.out.println("初始线程数:" + this.getPoolSize() + "\n");
// System.out.println("核心线程数:" + this.getCorePoolSize() + "\n");
// System.out.println("正在执行的任务数量:" + this.getActiveCount() + "\n");
System.out.println("已经执行的任务数量:" + this.getCompletedTaskCount() + "\n");
// System.out.println("任务总数:" + this.getTaskCount() + "\n");
// System.out.println("最大允许的线程数:" + this.getMaximumPoolSize() + "\n");
// System.out.println("线程空闲时间:" + this.getKeepAliveTime(TimeUnit.MILLISECONDS) + "\n");
super.afterExecute(r, t);
}
@Override
public void shutdown() {
System.out.println(String.format("已经执行的任务数: %s, 当前活动的线程数: %s,当前排队任务数: %s",
this.getCompletedTaskCount(), this.getActiveCount(), this.getQueue().size()));
super.shutdown();
}
}
测试:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
public class PoolTest implements Runnable {
private static ExecutorService es = ShenThreadPool.newCachedThreadPool();
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i ++) {
es.execute(new PoolTest());
}
es.shutdown();
}
}
6. Callable/Runnable 区别
线程池的执行任务有两种方法,一种是 submit、一种是 execute;这两个方法是有什么区别呢?
execute:
- execute 只可以接收一个 Runnable 的参数
- execute 如果出现异常会抛出
- execute 没有返回值
submit: - submit 可以接收 Runable 和 Callable 这两种类型的参数,
- 对于 submit 方法,如果传入一个 Callable,可以得到一个 Future 的返回值
- submit 方法调用不会抛异常,除非调用 Future.get
原理分析可见第二章