背景
- 常见的函数式接口,就是对函数编程的应用
- Runnable 没有返回值的函数式接口
- Callable 有返回值的函数式接口
使用线程池
- 一般来说,很少使用
new Thread(函数对象)
这种方式来直接 创建线程,更多的时候使用的线程成来集中管理线程,避免频繁开关线程造成的资源浪费
- 线程池对象的调用使用
submit
方法,传入一个函数对象
,可以使callable的,也可以是runnable的- 如果有返回值,返回值使用
Future
来接受,同时返回类型和Future的泛型保持一致
- 调用线程方法get之后,会开辟新的线程继续执行,但是主线程会一直
阻塞等待线程的返回结果
ExecutorService executorService = Executors.newFixedThreadPool(3);
Future<String> thread = executorService.submit(() -> {
System.out.println("线程1开始执行");
Thread.sleep(5000);
System.out.println("线程1执行完毕");
return "success1";
});
Future<String> thread2 = executorService.submit(() -> {
System.out.println("线程2开始执行");
Thread.sleep(10000);
System.out.println("线程2执行完毕");
return "success2";
});
try {
// 这种形式会将线程进行阻塞,等到两个线程执行完毕后才会继续执行
System.out.println("主线程开始执行");
String s = thread.get();
System.out.println("线程执行中");
String s1 = thread2.get();
System.out.println("线程执行完毕"+s+s1);
System.out.println("主线程执行完毕");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
上述方案的缺点
显式的使用线程池
,一般程序员对于线程池的使用并不熟练- 使用匿名函数的方式书写方法体,如果后续的方法体逻辑复杂的话,可能有很多嵌套,
代码的可读性将会变得很差
- 解决方案:使用1.8引入的
CompletableFuture
- 这个解决方案的优点,你可以将 数据的处理过程进行
拆分
,同时将上一步的结果返回给下一步进行处理,这种方式类似于前端的then ,这种解决方式类似于前端解决回调地狱的操作
同步消费 结果
thenAccept
将得到的结果同步消费
CompletableFuture<String> stringCompletableFuture1 = CompletableFuture.supplyAsync(() -> {
System.out.println("异步任务1开始");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("异步任务1结束");
return "异步任务1";
});
CompletableFuture<String> stringCompletableFuture2 = CompletableFuture.supplyAsync(() -> {
System.out.println("异步任务2开始");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("异步任务2结束");
return "异步任务2";
});
// 将得到的两个线程的任务 直接同步执行
stringCompletableFuture1.thenAccept(System.out::println);
stringCompletableFuture2.thenAccept(System.out::println);
System.in.read();// 阻塞主线程 主线程不要那么早结束
异步消费 结果
得到结果之后,将结果丢给一个consumer,
开启一个新的线程来消费这个结果
CompletableFuture<String> stringCompletableFuture1 = CompletableFuture.supplyAsync(() -> {
System.out.println("异步任务1开始");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("异步任务1结束");
return "异步任务1";
});
CompletableFuture<String> stringCompletableFuture2 = CompletableFuture.supplyAsync(() -> {
System.out.println("异步任务2开始");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("异步任务2结束");
return "异步任务2";
});
// 将得到的两个线程的任务 异步执行
stringCompletableFuture1.thenAcceptAsync(r -> {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(r);
System.out.println("异步任务1的结果消费完毕");
});
stringCompletableFuture2.thenAcceptAsync(r -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(r);
System.out.println("异步任务2的结果消费完毕");
});
System.in.read();// 阻塞主线程 主线程不要那么早结束
异步转换数据
一般来说thenApplyAsync 转换之后,得到的还是一个CompletableFuture,一般再转换之后还是需要一个Consumer进行消费
// 异步转换 再将转换后的结果同步消费
stringCompletableFuture1.thenApplyAsync(r -> {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(r);
System.out.println("异步任务1的结果消费完毕");
return r + "转换";
}).thenAccept(System.out::println);