Java 并发编程—— Executors 分析应用

  • 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 接口是继承 RunnableFuture 接口。所以 FutureTask 兼具 RunnableFuture 的特性,通过它的构造函数可以看到,Future 可以封装 Callable 对象然后作为提交任务。

这里有个关键点是 submit 方法的参数类型不同会引起返回值的 Future 不同。

事实上,FutureTaskFuture 接口的一个唯一实现类。

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 是一个泛型接口,返回值类型就是定义的泛型类型,所以一般我们配合 FutureTaskExecutorService 做任务提交以及获取。

Future submit(Callable task);
Future submit(Runnable task, T result);
Future<?> submit(Runnable task);

一般情况下我们使用第一个 submit 方法和第三个 submit 方法,第二个 submit 方法很少使用。大家可能会有疑惑,Runnable 对象没有返回值的,怎么能获取到返回值呢?这里其实是使用的 FutureTaskFutureTask 实现了 Runnable 接口。

2.4 submit 流程分析

先来看类的 UML 图:

ThreadPoolExecutor.png

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 类型参数概括一下,这段可能比较绕,所以多结合源码理解下过程:

  1. submit 方法中传入 Runnable 类型,一般为了获取结果,会将 Callable 对象构建成 FutureTask 类型在传入,(此处记作 FutureA);
  2. 调用 newTaskFor 方法生成 FutureTask 对象(记作 FutureB),这个对象就是我们 submit 方法返回的 Future 对象;
  3. FutureTask 的构造方法中调用 Executors.callable(runnable, result) 方法构建一个 Callable 对象存储在 FutureTask(即 FutureB) 的成员变量 callable 中。其中 result 默认为 null,由于传入的是 Runnable 类型,所以在构建的时候是通过新建一个 Callable 的子类 RunnableAdapter 进行封装。
  4. task 任务经过入队成功开始执行的时候,就是执行的 callablecall 方法。由于当前的 Callable 对象是 RunnableAdapter 类型,所以最终是调用传入的 RunnableFutureTask 类型)的 run 方法,并且返回值是 result
  5. 经过这样的一波三折,最终回到构建原始的 FutureTaskCallable 中调用 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 的逻辑大致可以分为以下步骤:

  1. 它们的主要作用是判断线程池的状态是否是运行状态,以及线程数是否超标。如果线程池是运行状态,并且线程没超标,则往下执行创建线程。
  2. 创建 Worker 对象。
  3. 添加 Worker 对象到集合。
  4. 获取 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 接口。它的内部包含一个 RunnableThread 对象,而这个 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 以自身为参数创建一个线程,当线程启动的时候就会执行 workerrun 方法。最终执行到 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(),由于 taskFutureTask 类型,所以程序运行到 FutureTaskrun 方法中。

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 中。

addworker.png 至此,整个的流程逻辑分析完毕。

3. 简单应用

下面通过一些简单的实例模拟下如何使用,主要有:

  • Future + Callable
  • FutureTask + Callable
3.1 Future + Callable

public class HelloWorld {

Future feature;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

针对最近很多人都在面试,我这边也整理了相当多的面试专题资料,也有其他大厂的面经。希望可以帮助到大家。

image

上述的面试题答案都整理成文档笔记。 也还整理了一些面试资料&最新2021收集的一些大厂的面试真题(都整理成文档,小部分截图)

image

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
/images/e5c14a7895254671a72faed303032d36.jpg" alt=“img” style=“zoom: 33%;” />

最后

针对最近很多人都在面试,我这边也整理了相当多的面试专题资料,也有其他大厂的面经。希望可以帮助到大家。

[外链图片转存中…(img-OZmbMHa3-1713112378003)]

上述的面试题答案都整理成文档笔记。 也还整理了一些面试资料&最新2021收集的一些大厂的面试真题(都整理成文档,小部分截图)

[外链图片转存中…(img-whkN5LcW-1713112378003)]

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值