Future的使用

/ 多线程 / 没有评论 / 366浏览

在开发多线程时,我们有时会需要返回子线程的处理结果,但不幸的是Runable接口是void类型没有返回值的。有人可能会想到用实例变量的方法实现此需求。实例变量的方式当然也是可以的,在其它文章中已经介绍过了,在使用实例变量时要特别注意,因为实例变量有可能会出现线程安全问题。其实在Java中已经为我们提供了这样的接口Callable接口。我们看一下Callable接口的定义。

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;
}

接口中只定义了一个方法call()方法,而不是run()方法,这点要特别注意,它们都可以实现线程的异步执行。下面我们来演示一下。

public class Test {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newCachedThreadPool();
Future<String> futures = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println(String.format("thread: %s", Thread.currentThread().getName()));
return null;
}
});
System.out.println(String.format("main: %s", Thread.currentThread().getName()));
}
}
main: main
thread: pool-1-thread-1

我们调用ExecutorService接口submit()方法来添加我们的任务,执行结果和Runable接口没有什么区别。唯一不同就是Runable接口要重写run()方法,Callable接口要重写call()方法。但Callable接口还有一个更强大的功能就是它支持返回类型。通过Callable接口可以返回异步执行后的结果。

public class Test {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newCachedThreadPool();
Future<String> future = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
String msg = "目录创建成功";
Thread.sleep(10000);
return msg;
}
});
System.out.println(String.format("main: %s\ttime: %s", Thread.currentThread().getName(), System.currentTimeMillis()));
System.out.println(future.get());
System.out.println(String.format("main: %s\ttime: %s", Thread.currentThread().getName(), System.currentTimeMillis()));
}
}
main: main	time: 1490611208158
目录创建成功
main: main	time: 1490611218159

ExecutorService接口submit()方法返回一个Future类型。用Future就可以获取Callable接口中异步执行后的任务数据。我们看两个main线程的输出时间有些不同。这是因为当调用Future接口中的get()方法时,当前线程会被阻塞,一直等待子任务的结束,因为子任务中我们延迟了10秒,所以这两个输出时间有些偏差。Future接口中还有其它几个方法。

isDone() // 判断任务是否执行完。 如执行完返回true 否则返回false
cancel() // 中断一个任务。当参数为false表示如果任务已经执行则不能中断当前任务,当参数为true时表示正在执行的任务也会中断
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
Future<String> future = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
String msg = "任务一";
for (int i = 3; i > 0; i--) {
System.out.println(String.format("倒计时:%s", i));
TimeUnit.SECONDS.sleep(1);
}
System.out.println(msg);
return msg;
}
});
TimeUnit.SECONDS.sleep(1);
System.out.println(String.format("cancel%s", future.cancel(false)));
System.out.println(String.format("future%s", future.get()));
}
}
倒计时:3
Exception in thread "main" java.util.concurrent.CancellationException
	at java.util.concurrent.FutureTask.report(FutureTask.java:121)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at Test.main(Test.java:28)
cancel:true
倒计时:2
倒计时:1
任务一

虽然抛出了异常,但任务还是执行完了。如果我们将参数修改为true,则线程立即中断并且不管当前线程是否执行完。

public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
Future<String> future = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
String msg = "任务一";
for (int i = 3; i > 0; i--) {
System.out.println(String.format("倒计时:%s", i));
TimeUnit.SECONDS.sleep(1);
}
System.out.println(msg);
return msg;
}
});
TimeUnit.SECONDS.sleep(1);
System.out.println(String.format("cancel%s", future.cancel(true)));
System.out.println(String.format("future%s", future.get()));
}
}
倒计时:3
cancel:true
Exception in thread "main" java.util.concurrent.CancellationException
	at java.util.concurrent.FutureTask.report(FutureTask.java:121)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at Test.main(Test.java:28)
get(long timeout, TimeUnit unit) // 指定阻塞时间,当超过时间时不在阻塞当前线程