java计算所有子线程结果_如何获取子线程的执行结果

前言

博主以前面试的时候,真真切切的被问过这样一道题:

如何获取子线程的执行结果?

总所周知,在单线程情况下,想获取线程执行结果很简单,只需要写类似的代码即可:

Object result = xxx.getXxx();

但是在多线程的情况下,想要获取子线程的执行结果,恐怕就没这么简单了。

剑走偏锋

我们都知道开启一个新线程的方式有两种:继承Thread类、实现Runnable接口。可是这两种方式都没有返回值,相信这也难不倒聪明的同学,可以把代码写成这样:

public class ThreadResultDemo {

private volatile static int result = 0;

public static void main(String[] args) throws InterruptedException {

Thread thread = new Thread(()->{

System.out.println("Hello Sicimike.");

// 要返回的结果

result = 1;

});

thread.start();

thread.join();

System.out.println("子线程返回的结果:" + result);

}

}

执行结果:

Hello Sicimike.

子线程返回的结果:1

可以看到主线程成功的拿到了子线程的执行结果。

e3637c4b90d784f04844587622222a38.png

类似的方法还有很多,不一一介绍了。功能虽然实现了,但是看起来总是不够优雅,还要考虑线程安全,并且代码不易维护。

Callable

JDK1.5开始,提供了一个能返回线程执行结果的接口Callable。接口定义非常简单,只有一个抽象方法

public interface Callable{

/**

* Computes a result, or throws an exception if unable to do so.

* 计算得到结果,如果无法计算就抛出异常

*/

V call() throws Exception;

}

毫无疑问,需要定义子类去实现Callable接口:

public class CallableTask implements Callable{

@Override

public Integer call() throws Exception {

Thread.sleep(1000);

System.out.println("Hello Sicimike.");

return 1;

}

}

接下来就需要把这个task放到Thread类中去执行了。但是看下一下Thread类

eaacecfa13803d8197ab7bcdc33d32be.png

Thread类总共有9个构造方法,但是没有一个构造方法能够接受传入Callable接口的子类。这样不就无解了吗?

想要子线程有返回值,就得实现Callable接口,实现了Callable接口就不能传递给Thread类。

这时我们需要借助JDK(JUC包中)提供的另一个类FutureTask

FutureTask

先看一下FutureTask如何解决这个问题:

/**

* @author sicimike

*/

public class ThreadResultDemo {

public static void main(String[] args) throws ExecutionException, InterruptedException {

// Callable接口任务(前文实现了Callable接口)

CallableTask task = new CallableTask();

// 包装成FutureTask类

FutureTaskfutureTask = new FutureTask<>(task);

new Thread(futureTask).start();

// 阻塞的方式获取线程执行结果

System.out.println(futureTask.get());

}

}

执行结果

Hello Sicimike.

1

纵观Thread类的9个构造方法,只传入一个参数的构造方法有2个:

public Thread(Runnable target) {......}

public Thread(String name) {......}

显然实例中调用的是第一个,也就是说FutureTask是Runnable接口的实现类。是FutureTask把Callable接口的子类转换成了Runnable接口的子类。

实现

先整体上看下FutureTask类的体系结构

c100766a3ac3a2f3b5a873773d665459.png

正如我们所料,FutureTask实现了Runnable接口。

接下来再具体看看FutureTask的构造方法

public FutureTask(Callablecallable) {

if (callable == null)

throw new NullPointerException();

this.callable = callable;

this.state = NEW; // ensure visibility of callable

}

通过这个构造方法,就可以在FutureTask中传入Callable实现类,而FutureTask又实现了Runnable接口,这样就完成了Callable接口到Runnable接口的转换。

除此之外,FutureTask还有一个构造方法

public FutureTask(Runnable runnable, V result) {

this.callable = Executors.callable(runnable, result);

this.state = NEW; // ensure visibility of callable

}

该构造方法可以把Runnable接口转换成Callable接口,继续跟踪callable方法的实现

/**

* java.util.concurrent.Executors.java

*/

public static Callablecallable(Runnable task, T result) {

if (task == null)

throw new NullPointerException();

return new RunnableAdapter(task, result);

}

static final class RunnableAdapterimplements 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类通过实现Callable接口的方式,实现call方法,在call方法里调用Runnable接口实现类的run方法。相当于把一个Runnable接口适配成了Callable接口。

我们都知道有一种套路叫设计模式,其中有一种叫适配器模式。

在设计模式中,适配器模式(英语:adapter pattern)有时候也称包装样式或者包装(wrapper)。将一个类的接口转接成用户所期待的。一个适配使得因接口不兼容而不能在一起工作的类能在一起工作,做法是将类自己的接口包裹在一个已存在的类中。

适配器模式通俗点讲就是:利用一个适配器,把一个不适合用户的接口适配成适合用户的接口,具体做法是将接口包裹在一个类中。

自此,Runnable接口和Callable接口实现了统一。

我想,这也是Oracle不把实现Callable接口称为第三种创建子线程的方法的原因吧。

根据前文FutureTask的类结构体系图可以看到,FutureTask除了实现Runnable接口外,还实现了Future接口。Future接口也正是FutureTask能获取子线程执行结果的原因。

Future

Future作为FutureTask的顶层接口,定义了五个管理线程任务的方法:

public interface Future{

/**

* Attempts to cancel execution of this task. This attempt will

* fail if the task has already completed, has already been cancelled,

* or could not be cancelled for some other reason. If successful,

* and this task has not started when [email protected] cancel} is called,

* this task should never run. If the task has already started,

* then the [email protected] mayInterruptIfRunning} parameter determines

* whether the thread executing this task should be interrupted in

* an attempt to stop the task.

*

* 尝试取消执行此任务

* 如果此任务已经完成、或者已经被取消、或者由于其他原因不能被取消,尝试会失败

* 如果取消成功,并且任务尚未开始执行,该任务永远不会执行

* 如果任务已经开始,由 mayInterruptIfRunning 参数决定是否中断正在执行该任务的线程,来停止该任务

*

*

After this method returns, subsequent calls to [email protected] #isDone} will

* always return [email protected] true}. Subsequent calls to [email protected] #isCancelled}

* will always return [email protected] true} if this method returned [email protected] true}.

*

* 该方法返回后,随后调用 isDone 方法总是返回true

* 如果该方法返回true,随后调用 isCancelled 方法总是返回true

*/

boolean cancel(boolean mayInterruptIfRunning);

/**

* Returns [email protected] true} if this task was cancelled before it completed

* normally.

*

* 如果此任务在正常执行完成之前被取消,该方法返回true

*/

boolean isCancelled();

/**

* Returns [email protected] true} if this task completed.

*

* Completion may be due to normal termination, an exception, or

* cancellation -- in all of these cases, this method will return

* [email protected] true}.

*

* 返回次任务是否完成(不管是正常执行完成,还是异常,还是被取消了)

*/

boolean isDone();

/**

* Waits if necessary for the computation to complete, and then

* retrieves its result.

* 等待计算完成,并返回结果(阻塞)

*

* @return the computed result

* @throws CancellationException if the computation was cancelled

* 如果计算被取消,抛出CancellationException异常

*

* @throws ExecutionException if the computation threw an exception

* 如果计算抛出异常,抛出ExecutionException异常

*

* @throws InterruptedException if the current thread was interrupted

* while waiting

* 如果线程在等待过程中被中断,抛出InterruptedException异常

*/

V get() throws InterruptedException, ExecutionException;

/**

* Waits if necessary for at most the given time for the computation

* to complete, and then retrieves its result, if available.

* 最多等待指定长度的时间,如果有结果就返回

*

* @throws CancellationException if the computation was cancelled

* 如果计算被取消,抛出CancellationException异常

*

* @throws ExecutionException if the computation threw an exception

* 如果计算抛出异常,抛出ExecutionException异常

*

* @throws InterruptedException if the current thread was interrupted

* while waiting

* 如果线程在等待过程中被中断,抛出InterruptedException异常

*

* @throws TimeoutException if the wait timed out

* 如果超时,抛出TimeoutException异常

*/

V get(long timeout, TimeUnit unit)

throws InterruptedException, ExecutionException, TimeoutException;

}

总结

本篇主要讲解了如何优雅的获取子线程的执行结果,Runnable接口和Callable接口的统一,FutureTask和Future接口。相信看完本篇之后,聪明的同学已经能把它应用到线程池了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值