前言

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

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

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

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

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

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

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类


java 子线程 控制 主线程 执行 java主线程获取子线程数据_java计算所有子线程结果

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类的体系结构


java 子线程 控制 主线程 执行 java主线程获取子线程数据_ci_02

正如我们所料,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接口。相信看完本篇之后,聪明的同学已经能把它应用到线程池了。