- exception
- @throws InterruptedException if the current thread was interrupted
- while waiting
- @throws TimeoutException if the wait timed out
*/
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
根据源码来看,Future
的功能定义就是:
- 判断任务是否完成;
- 能够中断任务;
- 能够获取任务执行结果。
2.2 FutureTask
直接看源码定义:
public class FutureTask implements RunnableFuture{
public FutureTask(Callable callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
}
public interface RunnableFuture extends Runnable, Future {
void run();
}
可以看出 FutureTask
实现 RunnableFuture
接口,而 RunnableFuture
接口是继承 Runnable
和 Future
接口。所以 FutureTask
兼具 Runnable
和 Future
的特性,通过它的构造函数可以看到,Future
可以封装 Callable
对象然后作为提交任务。
这里有个关键点是 submit
方法的参数类型不同会引起返回值的 Future
不同。
事实上,FutureTask
是 Future
接口的一个唯一实现类。
2.3 Callable
Callable
接口位于 java.util.concurrent
包下,在它里面也只声明了一个方法,Callable
接口的定义跟 Runnable
类似:
public interface Callable {
/**
- 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;
}
可以看到 Callable
是一个泛型接口,返回值类型就是定义的泛型类型,所以一般我们配合 FutureTask
和 ExecutorService
做任务提交以及获取。
Future submit(Callable task);
Future submit(Runnable task, T result);
Future<?> submit(Runnable task);
一般情况下我们使用第一个 submit
方法和第三个 submit
方法,第二个 submit
方法很少使用。大家可能会有疑惑,Runnable
对象没有返回值的,怎么能获取到返回值呢?这里其实是使用的 FutureTask
,FutureTask
实现了 Runnable
接口。
2.4 submit 流程分析
先来看类的 UML
图:
1. submit 方法
通过上面的 UML
图可知,submit
方法是在 ExecutorService
接口中定义,由 AbstractExecutorService
类进行实现。
protected RunnableFuture newTaskFor(Runnable runnable, T value) {
return new FutureTask(runnable, value);
}
protected RunnableFuture newTaskFor(Callable callable) {
return new FutureTask(callable);
}
/**
- @throws RejectedExecutionException {@inheritDoc}
- @throws NullPointerException {@inheritDoc}
*/
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
/**
- @throws RejectedExecutionException {@inheritDoc}
- @throws NullPointerException {@inheritDoc}
*/
public Future submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
/**
- @throws RejectedExecutionException {@inheritDoc}
- @throws NullPointerException {@inheritDoc}
*/
public Future submit(Callable task) {
if (task == null) throw new NullPointerException();
RunnableFuture ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
可以看到在 submit
方法中,最终都是转换成 RunnableFuture
对象,而这个 RunnableFuture
对象本质是指向 FutureTask
类型。在最终执行的时候又都采用了 execute
方法进行执行。
前面我们提到 submit
方法中传入的任务类型会影响返回值,究竟是哪里的问题呢? 首先看传入 task
类型是 Callable
类型,调用 newTaskFor
方法生成 FutureTask
对象。
public FutureTask(Callable callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
所以在后续的执行中就调用的是 callbale
对象的 call
方法执行,并将结果存储在运行的 FutureTask
中进行返回,正常获取。
如果传入的类型是 Runnable
,同样调用 newTaskFor
方法生成 FutureTask
对象。
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
public static Callable callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter(task, result);
}
static final class RunnableAdapter implements Callable {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
可以看到,经过一系列的转折,最终是转换成一个 RunnableAdapter
,这个 RunnableAdapter
的值就是传入的 result
,没传入就是 null
。
针对 Runnable
类型参数概括一下,这段可能比较绕,所以多结合源码理解下过程:
submit
方法中传入Runnable
类型,一般为了获取结果,会将Callable
对象构建成FutureTask
类型在传入,(此处记作FutureA
);- 调用
newTaskFor
方法生成FutureTask
对象(记作FutureB
),这个对象就是我们submit
方法返回的Future
对象; - 在
FutureTask
的构造方法中调用Executors.callable(runnable, result)
方法构建一个Callable
对象存储在FutureTask
(即FutureB
) 的成员变量callable
中。其中result
默认为null
,由于传入的是Runnable
类型,所以在构建的时候是通过新建一个Callable
的子类RunnableAdapter
进行封装。 - 当
task
任务经过入队成功开始执行的时候,就是执行的callable
的call
方法。由于当前的Callable
对象是RunnableAdapter
类型,所以最终是调用传入的Runnable
(FutureTask
类型)的run
方法,并且返回值是result
。 - 经过这样的一波三折,最终回到构建原始的
FutureTask
的Callable
中调用call
方法,计算结果就被存储在传入作为参数的FutureTask
中,而返回值的Future
结果就是result
。
所以在 FutureTask + Callable
结合使用时,如果通过 submit
返回值来获取计算结果就会出现为 null
的情况。
在 ThreadPoolExecutor
的介绍中,我们针对 execute
进行了大致的流程介绍,并没有涉及到实际的执行流程,所以在这里我们针对 submit
方法的执行捋一遍流程。
2. addWorker
private boolean addWorker(Runnable firstTask, boolean core) {
/**
- 此处省略 n 行代码,它们的主要作用是判断线程池的状态是否是运行状态,以及线程数是否超标。
- 如果线程池是运行状态,并且线程没超标,则往下执行创建线程。
*/
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 将 firstTask 封装成 Worker对象。
w = new Worker(firstTask);
// 获取封装的任务线程
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 重新检查线程池的状态
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// 线程池是运行状态 或 是关闭状态但是没任务,则添加 work 到集合
workers.add(w);
// 获取添加后的集合大小,修改 poolSize 的值
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;
}
addWorkder
的逻辑大致可以分为以下步骤:
- 它们的主要作用是判断线程池的状态是否是运行状态,以及线程数是否超标。如果线程池是运行状态,并且线程没超标,则往下执行创建线程。
- 创建
Worker
对象。 - 添加
Worker
对象到集合。 - 获取
Worker
线程并执行。
我们想要获得最终的执行转换,如何转到我们定义的接口,就需要扒下 Worker
的外衣来看看。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
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;
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
// 省略 n 行代码
}
首先需要明确 Worker
类是 ThreadPoolExecutor
的内部类。Worker
类是集成 AbstractQueuedSynchronizer
的子类,AbstractQueuedSynchronizer
应该都熟悉了(前面我们在并发编程锁的系列介绍过),同时实现了 Runnable
接口。它的内部包含一个 Runnable
和 Thread
对象,而这个 Thread
对象是通过创建。
this.thread = getThreadFactory().newThread(this);
将自身作为一个参数进行创建。getThreadFactory()
方法 ThreadPoolExecutor
提供的获取 ThreadFactory
方法,最终的实现是在 Executor
的内部类 DefaultThreadFactory
中进行实现。
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = “pool-” +
poolNumber.getAndIncrement() +
“-thread-”;
}
public Thread newThread(Runnable r) {
// 将我们的任务 Runnable 对象作为参数常见 Thread
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
// 判断是否是守护线程,如果是守护线程,设为为 false,让它不是守护线程
if (t.isDaemon())
t.setDaemon(false);
// 设置线程的优先级为普通
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
这样 Worker
以自身为参数创建一个线程,当线程启动的时候就会执行 worker
的 run
方法。最终执行到 runWorker(this)
。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
// 获取 Worker 封装的 task 任务
Runnable task = w.firstTask;
// 销毁 workder 对象的 Runnable 引用,并释放锁
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
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 {
// 在 task 执行前,调用 beforeExecute 方法抛出异常,task 不会执行。该方法是空。
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;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
这里的关键方法 task.run()
,由于 task
是 FutureTask
类型,所以程序运行到 FutureTask
的 run
方法中。
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
// 调用自定义的 callable 对象的 call 方法,获取计算结果
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);
}
}
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
最终,所有的执行和结果存储都回归到 FutureTask
中。
至此,整个的流程逻辑分析完毕。
3. 简单应用
下面通过一些简单的实例模拟下如何使用,主要有:
Future + Callable
FutureTask + Callable
3.1 Future + Callable
public class HelloWorld {
Future feature;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
最后
针对最近很多人都在面试,我这边也整理了相当多的面试专题资料,也有其他大厂的面经。希望可以帮助到大家。
上述的面试题答案都整理成文档笔记。 也还整理了一些面试资料&最新2021收集的一些大厂的面试真题(都整理成文档,小部分截图)
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
/images/e5c14a7895254671a72faed303032d36.jpg" alt=“img” style=“zoom: 33%;” />
最后
针对最近很多人都在面试,我这边也整理了相当多的面试专题资料,也有其他大厂的面经。希望可以帮助到大家。
[外链图片转存中…(img-OZmbMHa3-1713112378003)]
上述的面试题答案都整理成文档笔记。 也还整理了一些面试资料&最新2021收集的一些大厂的面试真题(都整理成文档,小部分截图)
[外链图片转存中…(img-whkN5LcW-1713112378003)]
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!