第11章 异步任务控制
3.1 Future
3.1.1 定义任务的三种方式
最初学习多线程时就学习了什么是任务,什么是线程,线程用来执行任务,任务包含业务逻辑,创建一个任务并执行任务有哪些方式呢?下边进行汇总。
1、继承Thread类。
Thread类本身实现了Runnable接口,继承Thread类重写run方法可以创建一个任务,调用thread对象的start即可启动一个线程并执行任务,由于Java是单继承模式,继承Thread类将无法再继承其它类,所以不推荐使用此方式。
2、实现java.lang.Runnable接口
实现java.lang.Runnable接口定义独立的任务对象,有如下方法:
1)使用Thread类的Thread(Runnable target) 构造方法
2)使用ExecutorService的void execute(Runnable command)
3、实现java.util.concurrent.Callable接口
与Runnable接口不同的是Callable接口有返回值,Callable接口如下:
public interface Callable<V> {
V call() throws Exception;
}通过ExecutorService的Future submit(Callable task)方法提交Callable任务得到一个Future返回值,Future表示未来可能返回的结果,通过Future接口的方法可以获取任务的返回值,判断任务是否完成等操作,如下:

get()和get(long,TimeUnit)两个方法可以获取任务执行结果,两个方法都是阻塞方法,其中get()直到任务完成为止,get(long,TimeUnit)方法可以设置超时时间,如果达到超时时间任务还没有结束则抛出异常。
isDone():非阻塞方法,获取任务是否完成。
isCancelled(): 非阻塞方法,如果任务完成前被终止则返回true。
cancel(boolean)取消任务。
查询API文档如下:

3.1.2 定义具有返回值的任务
下边测试如何定义一个具有返回值的任务。
定义一个带有返回值的任务需要实现Callable接口,如下:
@FunctionalInterface
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;
}下边实现求1到100这几个数的和,测试代码如下:
package com.yjoffer.javase.thread.utils;
import com.yjoffer.javase.config.Logger;
import java.util.concurrent.*;
/**
* 测试任务控制
* @author 预见猿份(www.yjoffer.com)
* @version 1.0
**/
public class FutureTest {
private static Logger logger = Logger.getLogger(FutureTest.class);
//测试Future
public static void test_future(){
ExecutorService threadPool = Executors.newCachedThreadPool();
//定义一个future
Future<Long> future = threadPool.submit(() -> {
logger.debug("开始任务");
long sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//返回结果
logger.debug("返回结果");
return sum;
});
//获取结果
try {
logger.debug("获取结果");
Long result = future.get();
logger.debug("result="+result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
test_future();
}
}输出:
15:42:23.120 FINE com.yjoffer.javase.thread2.basic.FutureTest[main] - 获取结果
15:42:23.120 FINE com.yjoffer.javase.thread2.basic.FutureTest[pool-1-thread-1] - 开始任务
15:42:28.142 FINE com.yjoffer.javase.thread2.basic.FutureTest[pool-1-thread-1] - 返回结果
15:42:36.38 FINE com.yjoffer.javase.thread2.basic.FutureTest[main] - result=5050在future.get();处打断点,get()只是获取结果不影响 任务的执行。
get(long,TimeUnit)等待指定时间后,如果任务没有执行完成则抛出异常java.util.concurrent.TimeoutException。
下边的代码执行任务至少需要5秒,get()方法等待3秒,所以最终由于get()方法的超时提前到达导致抛出TimeoutException异常,测试代码如下:
//测试Future,测试get方法超时
public static void test_future2(){
ExecutorService threadPool = Executors.newCachedThreadPool();
//定义一个future
Future<Long> future = threadPool.submit(() -> {
logger.debug("开始任务");
long sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//返回结果
logger.debug("返回结果");
return sum;
});
//获取结果
try {
logger.debug("获取结果");
Long result = future.get(3,TimeUnit.SECONDS);
logger.debug("result="+result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}运行程序,输出:
15:45:43.38 FINE com.yjoffer.javase.thread2.basic.FutureTest[main] - 获取结果
15:45:43.38 FINE com.yjoffer.javase.thread2.basic.FutureTest[pool-1-thread-1] - 开始任务
java.util.concurrent.TimeoutException
at java.util.concurrent.FutureTask.get(FutureTask.java:205)
at com.yjoffer.javase.thread2.basic.FutureTest.test_future2(FutureTest.java:75)
at com.yjoffer.javase.thread2.basic.FutureTest.main(FutureTest.java:88)
15:45:48.64 FINE com.yjoffer.javase.thread2.basic.FutureTest[pool-1-thread-1] - 返回结果3.1.3 异常处理
如果任务在执行时出现异常,外界可以拿到异常信息吗?
测试代码如下:
//测试Future,测试异常处理
public static void test_future3(){
ExecutorService threadPool = Executors.newCachedThreadPool();
//定义一个future
Future<Long> future = threadPool.submit(() -> {
logger.debug("开始任务");
long sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
int i=1/0;//制造异常
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//返回结果
logger.debug("返回结果");
return sum;
});
//获取结果
try {
logger.debug("获取结果");
Long result = future.get();
logger.debug("result="+result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
logger.debug("异常处理...");
e.printStackTrace();
}
}运行程序,输出如下:
15:48:17.900 FINE com.yjoffer.javase.thread2.basic.FutureTest[main] - 获取结果
15:48:17.900 FINE com.yjoffer.javase.thread2.basic.FutureTest[pool-1-thread-1] - 开始任务
15:48:17.929 FINE com.yjoffer.javase.thread2.basic.FutureTest[main] - 异常处理...
java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at com.yjoffer.javase.thread2.basic.FutureTest.test_future3(FutureTest.java:112)
at com.yjoffer.javase.thread2.basic.FutureTest.main(FutureTest.java:124)
Caused by: java.lang.ArithmeticException: / by zero
at com.yjoffer.javase.thread2.basic.FutureTest.lambda$test_future3$2(FutureTest.java:98)
at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
at java.util.concurrent.FutureTask.run(FutureTask.java)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)从输出可以看出:当任务执行过程出现异常,通过捕获get()方法异常即可获取。
请免费关注 预见猿份 公众号,发送:yjoffer.com,获取解锁密码
微信扫码关注关注公众号回复获取密码后,在下方输入并点击解锁,本机后续再读无需重复输入。
