随手记
问题背景:
使用FutureTask来做异步任务,但是当需要获取返回值,futureTask.get()方法却是非异步执行的,不符合需求,如果还想在任务完成后记录日志什么的,更不用考虑了。
问题研究:
使用CompletableFuture代替FutureTask。
CompletableFuture简单使用方式如下:
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{
System.out.println("future task is called");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("future task is done");
return "future task is done";
});
future.thenAccept((result) -> {
System.out.println("执行完成!");
});
future.exceptionally(t -> {
System.out.println("执行失败!"+t.getMessage());
return null;
});
// Thread.sleep(3000);
}
结果输出:
是的,问题来了,当使用main方法测试时,发现主线程执行完毕后,程序就会结束,导致任务执行完毕回调的方法没有触发。当设置sleep3秒时,才可以打印出来。
public static void main(String[] args) throws InterruptedException {
CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{
System.out.println("future task is called");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("future task is done");
return "future task is done";
});
future.thenAccept((result) -> {
System.out.println("执行完成!");
});
future.exceptionally(t -> {
System.out.println("执行失败!"+t.getMessage());
return null;
});
Thread.sleep(3000);
}
结果:
但是,实际应用中,谁也不知道任务要执行多久,这种方法好像有点问题......
问题解决:
但是后来又仔细一想,又好像有点不对,实际的应用场景不需要考虑主线程结束的问题。
因为这个这个任务是放到springboot网站中跑的,而springboot的主线程好像不会停!不然网站就挂了啊!
从main转到网站中测试后,果然没问题。
public Object work(FutureUtils job, DownloadTask downloadTask, DownloadTaskService downloadTaskService) {
CompletableFuture<Object> future = CompletableFuture.supplyAsync(job::run);
future.thenAccept((result) -> {
Date ed = new Date();
downloadTask.setEndTime(ed);
downloadTask.setTaskStatus("S");
downloadTask.setTaskProgress(100.0);
downloadTask.setUsingTime(ed.getTime() - downloadTask.getStartTime().getTime());
downloadTaskService.downloadTaskUpdate(downloadTask);
System.out.println("执行完成!");
});
future.exceptionally(e -> {
String msg = e.toString();
log.error("任务执行失败!{}", msg);
Date ed = new Date();
downloadTask.setEndTime(ed);
downloadTask.setTaskStatus("E");
downloadTask.setUsingTime(ed.getTime() - downloadTask.getStartTime().getTime());
downloadTask.setMsg(msg.substring(0, Math.min(msg.length(), 2000)));
downloadTaskService.downloadTaskUpdate(downloadTask);
return null;
});
return null;
}
坑一:
如果定义的job有异常抛出,千万不要使用catch去捕获,否则外层的 future.exceptionally无法捕获异常,无法统一记录日志。
但是如果把job中的异常往外抛,则job方法的定义势必会声明有异常抛出,那问题来了,在CompletableFuture.supplyAsync中自定义的方法
new Supplier<Object>(){},它只有一个方法get,而这个get是override的,父类并没有抛出异常,也就是说,这个异常抛不出了......而在里面catch了,就无法统一异常处理。
还好有个nb的注解@SneakyThrows,这样就不需要抛异常,也不需要catch了。有异常时,future.exceptionally也可以正常捕获!
CompletableFuture<Object> future = CompletableFuture.supplyAsync(new Supplier<Object>() {
@SneakyThrows
@Override
public Object get() {
return job.run();
}
});
多任务拓展:
多任务异步执行,一批任务都执行完毕后,执行某个方法:
@Data
public class User {
private String name;
private String age;
User(){}
User(String name,String age){
= name;
this.age = age;
}
}
public class CompleteFutureTest {
private static List<User> shops = Arrays.asList(new User("shop1","1"),
new User("shop2","1"),
new User("shop3","1"),
new User("shop4","1"),
new User("shop5","1"),
new User("shop6","1"),
new User("shop7","1"),
new User("shop8","1")
);
public static void main(String[] args) throws InterruptedException {
//定义任务列表
List<CompletableFuture<String>> priceFuture = shops.stream().map(shop -> CompletableFuture
.supplyAsync(() -> String.format("%s price is %s ", shop.getName(), shop.getAge())))
.collect(Collectors.toList());
//定义每个任务完成后做的事情
priceFuture.forEach(futureResult ->{
futureResult.thenAccept(CompleteFutureTest::allDone);
});
// 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:
Thread.sleep(30000);
}
public static final int count = shops.size();
public static AtomicInteger atomicInteger = new AtomicInteger(0);
static void allDone(String s) {
int result = atomicInteger.addAndGet(1);
System.out.println(result);
if(count == result){
System.out.println("all done");
}
}
}