文章目录
什么是异步编程
Java 异步编程是指在程序中使用非阻塞的方式处理任务和操作,以提高系统的性能和响应能力。使用异步编程可以使得程序在执行耗时操作时不会阻塞主线程,而是通过回调、Future 或 CompletableFuture 等机制来处理异步操作的结果。
它允许多个事情同时发生,当程序调用需要长时间运行的方法时,它不会阻塞当前的执行流程,程序可以继续运行,当方法执行完成时通知给主线程根据需要获取其执行结果或者失败异常的原因
异步编程案例:泡茶
public static void main(String[] args) {
// 任务1:洗水壶--> 烧开水 (同步的)
CompletableFuture<Void> f1 = CompletableFuture.runAsync(() -> {
System.out.println("T1:洗水壶。。。");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T1:烧开水。。。");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
//任务2:洗茶壶->洗茶杯-->拿茶叶
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> {
System.out.println("T2:洗茶壶");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T2:洗茶杯");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T2:拿茶叶");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "龙井";
});
//任务3:任务1和2完成后 泡茶
CompletableFuture<String> f3 = f1.thenCombine(f2, (a, b) -> {
System.out.println("拿到茶叶" + b);
System.out.println("泡茶...");
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "上茶" + b;
});
// 等待任务3执行结果
System.out.println("f3.join() = " + f3.join());
}
一、Future & FutureTask
Future:
Future接口定义了一些操作异步任务执行一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务执行是否完毕
FutureTask是Future的实现类;FutureTask --> RunnableFuture --> Runnable、Future
比如主线程让子线程去执行任务,子线程可能比较耗时,启动子线程后。主线程可以继续做其他事情,忙完其他事情 或者先执行完,过了一会才去获取子线程执行结果或者变更的任务状态。
- 异步多线程特点
异步多线程任务执行且返回有结果,三个特点:多线程、有返回、异步任务
- Future提供了一种异步并行计算的功能
- Runnable接口:无返回值
- Callable接口:有返回值
- Thread类:具有创建线程的能力
FutureTask 如何实现3个特点
首先创建一个线程得:创建Thread类
构造方法,必须得传入一个Runnable的实现类:
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
RunnableFuture接口:继承了Runnable、Future。有创建线程、异步的能力,但没有返回结果的能力
FutureTask类:实现了RunnableFuture,有创建线程、异步的能力。构造注入的方式
构造方法,传入Callable实现类,拥有了返回值的能力,至此具备了3个必要条件:
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
FutureTask 类图
FutureTask 缺点
- get() 阻塞:一旦调用get方法,如果计算没有完成就会一直阻塞等待
- isDone轮询:轮询容易导致cpu空转,浪费更多的资源
总结:FutureTask对结果的获取不太友好!
二、CompletableFuture
它是Future的功能增强版 ,减少阻塞和轮询!
-
CompletionStage
代表异步计算过程中的某一个阶段,一个阶段完成可能会触发另外一个阶段。 -
CompletableFuture
- 在java8中他提供了非常强大的Future的扩展功能,可以简化异步编程的复杂性,提供了函数式编程能力,可以通过回调的方式处理计算结果,也提供了转换和组合CompletableFuture的方法
- 他可能代表一个明确完成的Future,也可以代表一个完成阶段(CompletionStage),它支持计算完成后触发一些函数或执行某些动作
4个核心静态方法,创建一个异步任务
1. runAsync 无返回值
a. public static CompletableFuture<Void> runAsync(Runnable runnable) {}
b. public static CompletableFuture<Void> runAsync(Runnable runnable,Executor executor){}
2. supplyAsync 有返回值(常用)
a. public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {}
b. public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor){}
- 上面executor参数指的是线程池,没有指定的话用的是ForkJoinPool.commonPool() 作为线程池执行异步代码,指定的话就是用用户的线程池
优点:
- 异步任务结束后,会自动调用某个对象的方法
- 主线程设置好回调后,不在关心异步任务的执行,异步任务之间可以顺序执行
- 异步任务出错时,会自动调用某个对象的方法
CompletableFuture 常用方法
1. 获取结果和触发计算
1. completableFuture.join(); 等于get方法,join不用处理异常
2. completableFuture.get();
3. completableFuture.getNow("aaa")
4. completableFuture.getNow("aaa") 方法执行时获取异步io的值,获取到返回值。获取不到则返回aaa
//如果尚未完成,则将 {@link get()} 和相关方法返回的值设置为给定值
// complete(打断) :执行时获取不到异步IO的值,返回true,把aaa传入,此后join、get获取的值就是aaa了
5. completableFuture.complete("aaa")
2. 对计算结果进行处理
- thenApply —>计算结果存在依赖关系,这两个线程串行化---->由于存在依赖关系(当前步错,不走下一步),当前步骤有异常的话就叫停
- handle —>计算结果存在依赖关系,这两个线程串行化---->有异常也可以往下走一步
public class CompletableFutureApiDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 1;
}, threadPool).thenApply(f -> {
System.out.println("222");
return f + 2;
}).handle((f, e) -> {
System.out.println("3333");
int i=10/0;
return f + 2;
// thenApply(f -> {
// System.out.println("3333");
// return f + 2;
}).whenComplete((v, e) -> {
if (e == null) {
System.out.println("----计算结果" + v);
}
}).exceptionally(e -> {
e.printStackTrace();
System.out.println(e.getCause());
return null;
});
System.out.println(Thread.currentThread().getName() + "------主线程先去做其他事情");
}
}
3. 对计算结果进行消费
○ thenAccept : 接受任务的处理结果,并消费处理,无返回结果
○ thenRun(Runnable runnable) :任务A执行完执行B,并且不需要A的结果
○ thenAccept(Consumer action): 任务A执行完执行B,B需要A的结果,但是任务B没有返回值
○ thenApply(Function fn): 任务A执行完执行B,B需要A的结果,同时任务B有返回值
thenAccept:
public class CompletableFutureApi2Demo {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
CompletableFuture.supplyAsync(() -> {
return 1;
}, threadPool).thenApply(f -> {
return f + 2;
}).thenApply(f -> {
return f + 2;
}).thenAccept(r -> {
System.out.println(r);//5
});
}
}
补充:
public class CompletableFutureApi2Demo {
public static void main(String[] args) {
System.out.println(CompletableFuture.supplyAsync(() -> "result").thenRun(() -> {}).join());//null
System.out.println(CompletableFuture.supplyAsync(() -> "result").thenAccept(r -> System.out.println(r)).join());//result null
System.out.println(CompletableFuture.supplyAsync(() -> "result").thenApply(f -> f + 2).join());//result2
}
}
4. 对计算结果进行选用
○ 谁快用谁
○ applyToEither : 有返回值
○ acceptEither:consumer接口,无返回值,直接输出
CompletableFuture<String> palyA = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "playA";
});
CompletableFuture<String> palyB = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "playB";
});
// 根据速度选择
palyA.acceptEither(palyB,r->{
System.out.println(r + " is winner");
});
palyA.applyToEither(palyB,f->{
System.out.println(f+" is winner");
return f;
}).join();
5. 对计算结果进行合并
○ 两个CompletableStage任务都完成后,最终能把两个任务的结果一起交给thenCombine来处理
○ 先完成的先等着,等待其他分支任务
CompletableFuture<String> palyA = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "playA";
});
CompletableFuture<String> palyB = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "playB";
});
// 合并两个结果
palyA.thenCombine(palyB,(a,b)->{
return a.concat("---").concat(b);
}).join();
CompletableFuture 常用接口
# 创建一个异步任务
CompletableFuture#supplyAsync
# 创建一个特殊的异步任务,当其关联的异步任务任意一个完成时就返回
CompletableFuture#anyOf
# 创建一个特殊的异步任务,当其关联的异步任务都完成时就返回
CompletableFuture#allOf
# 当发生异常时的处理逻辑:注意,出现异常后,执行逻辑还会继续,并不会跳出来
CompletableFuture#exceptionally
# 继续处理结果,并返回新的结果
CompletableFuture#thenApply
# 与其他的异步任务结合,都执行完毕后再执行新的任务
CompletableFuture#thenCombine
# 继续异步处理结果,并返回新任务对应的CompletableFuture。与thenApplyAsync很类似,但是返回结果更加友好,类似于stream中的flatMap
CompletableFuture#thenCompose
# 处理最后的结果
CompletableFuture#thenAccept
CompletableFuture和线程池说明
■ 如果没有传入自定义线程池,都用默认线程池ForkJoinPool
■ 传入一个线程池,如果你执行第一个任务时,传入了一个自定义线程池
● 调用thenRun方法执行第二个任务时,则第二个任务和第一个任务时共用同一个线程池
● 调用thenRunAsync执行第二个任务时,则第一个任务使用的是你自定义的线程池,第二个任务使用的是ForkJoin线程池
■ 备注:可能是线程处理太快,系统优化切换原则, 直接使用main线程处理,thenAccept和thenAcceptAsync,thenApply和thenApplyAsync等,之间的区别同理。
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
try {
CompletableFuture.supplyAsync(() -> {
try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println("1号任务" + Thread.currentThread().getName());
return "playA";
},threadPool).thenRunAsync(()->{
try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println("2号任务" +Thread.currentThread().getName());
}).thenRun(()->{
try {TimeUnit.MILLISECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println("3号任务" +Thread.currentThread().getName());
}).join();
} finally {
threadPool.shutdown();
}
}
// 1号任务pool-1-thread-1
// 2号任务ForkJoinPool.commonPool-worker-9
// 3号任务ForkJoinPool.commonPool-worker-9