- FutureTask
在 JDK1.8 后的异步编排API中的CompletableFuture,提供了 异步任务的成功回调、异常回调。
public class FutureTaskTest {
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return UUID.randomUUID().toString();
});
future.thenAccept((s) -> {
System.out.println("异步任务的返回值" + s);
});
future.exceptionally((ex) -> {
ex.printStackTrace();
return null;
});
Thread.sleep(5000);
}
}
启动一个异步任务后,就相当于启动了一个线程,给这个异步对象设置回调方法,那么当异步任务运行结束或者出现异常时会自动调用相关的回调。
- 自定义AsyncTask实现 异步任务回调
public class AsyncTest {
public static void main(String[] args) throws Exception {
AsyncTask<String> asyncTask = new AsyncTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("开始处理数据");
Thread.sleep(3000);
String s = UUID.randomUUID().toString();
System.out.println("数据计算完成");
return s;
}
});
asyncTask.run();
Thread.sleep(5000);
asyncTask.thenError((ex) -> {
ex.printStackTrace();
});
asyncTask.thenSuccess((s) -> {
System.out.println("任务数据:" + s);
});
System.out.println("主线程结束");
}
}
class AsyncTask<T>{
private Callable<T> task;
private T value;
private Exception e;
private Consumer<T> successConsumer;
private Consumer<? super Exception> exConsumer;
private Thread thread;
public AsyncTask(Callable<T> task){
this.task = task;
}
public void run(){
this.thread = new Thread(() -> {
try {
value = task.call();
if (successConsumer != null){
successConsumer.accept(value);
}
} catch (Exception e) {
this.e = e;
if (exConsumer != null){
exConsumer.accept(e);
}
}
});
this.thread.start();
}
public void thenSuccess(Consumer<T> consumer){
this.successConsumer = consumer;
if (this.thread != null && !this.thread.isAlive() && e == null){
consumer.accept(value);
}
}
public void thenError(Consumer<? super Exception> consumer){
this.exConsumer = consumer;
if (this.thread != null && !this.thread.isAlive() && e != null){
consumer.accept(e);
}
}
}
异步任务回调 就是想让线程运行完成后执行其他线程设置的回调方法。
这里通过 thenSuccess 设置异步线程的回调方法 ,然后通过以下代码去判断是否已经设置了异步回调。
if (successConsumer != null){
successConsumer.accept(value);
}
这里还有一个问题,主线程是先调用了run方法,再调用了thenSuccess方法,如果run方法和thenSuccess方法之前的执行时间超过了异步任务的执行时间。
避免这种问题的解决办法有2中。
-
在run之前先调用thenError 、thenSuccess 设置好回调后,再调用run。
由于在开启线程之前就已经设置好回调了,自然不用担心。 -
在thenError、thenSuccess 方法内判断线程的状态,如果线程的isAlive方法返回false,则说明线程已经运行完了,在运行中的线程状态是true,所以,将线程的返回值以及异常保存起来,在调用 thenError、thenSuccess 方法 时,判断线程如果运行完成(调用了start方法的线程状态会是true),这个时候说明还没有执行回调,则拿到返回值后调用回调方法即可。
if (this.thread != null && !this.thread.isAlive() && e == null){
consumer.accept(value);
}
- 泛型
关于泛型的理解,泛型类可以想象成一个翁,翁就是一个容器,可以装各种各样的东西,每个翁都是如此,但是为了方便装的东西后续可以快速找到,就给每一个翁贴上一个标签,例如第一个翁只装钥匙,第二个翁只装杯子等等,这样从翁中拿出来的时候也只会是放进入的东西。泛型就相当于翁的一个标签,标识着此翁装配的东西,
一个翁也就是是带泛型的Java对象,在当作参数传递的过程中翁上的标签也是会传递的,Java编译器会自动检查带泛型的对象返回的类型以及当作参数传入的对象类型,例如 stream的map方法在使用lambda返回时 会自动推断返回值类型,如果接受的类型不匹配,编译器会报错,再比如使用lambda表达式定义入参时,无需指定参数类型,如果泛型类型是String类型,那就调不了其他类的方法,这也是有编译器保证的,这就是泛型的自动推断和传递。
有的实例方法需要的参数也是带着泛型的,例如AsyncTask中的
public void thenSuccess(Consumer<T> consumer){
this.successConsumer = consumer;
if (this.thread != null && !this.thread.isAlive() && e == null){
consumer.accept(value);
}
}
public void thenError(Consumer<? super Exception> consumer){
this.exConsumer = consumer;
if (this.thread != null && !this.thread.isAlive() && e != null){
consumer.accept(e);
}
}
例如thenSuccess 方法需要一个Consumer 实例,而这个实例也是需要带标签的,那这个标签是什么呢?就是取决于当前对象的标签,例如我有一个翁,标签是 钥匙,那我需要一个处置东西的工具、而这个处置东西的工具也可以打标签,例如可以处置杯子、处置木头、处置钥匙等,那这个地方的意思就是我需要一个Consumer类型的对象,这个对象的标签是T,而T是哪里来的,T是在创建当前对象的时候定义好的,也就是类似常说的出厂设置,这个实例在创建的时候就知道自己的泛型,即 某个翁在制造好出厂的时候就打好标签了。那自然 此对象在当作参数传递时,编译器也会自动检查标签是否与某个方法参数相匹配等等。