Java线程池以及Future和CompletableFuture的用法
参考:https://blog.csdn.net/weixin_50330544/article/details/131687150
1.线程池
为什么使用线程池?
频繁申请/销毁资源和调度资源,将带来额外的消耗,可能会非常巨大。
对资源无限申请缺少抑制手段,易引发系统资源耗尽的风险。
系统无法合理管理内部的资源分布,会降低系统的稳定性。
使用线程池的好处?
重用存在的线程,减少对象创建、消亡的开销。
有效的控制最大并发数,提高系统资源使用率。
统一的分配、调优和监控、可定时执行、定期执行。
线程池所在包java.util.concurrent
顶级接口Executor,真正的线程池接口是ExecutorService
Executors类提供创建线程池的方法
Java内置的线程池
自定义线程池
当线程池满了之后就会执行拒绝策略。线程池数量等于最大线程数+阻塞队列数
1、AbortPolicy
当任务添加到线程池中被拒绝时,它将抛出RejectedExecutionException异常。(该策路下,直接丢弃任务,并抛出RejectedExecutionException异常)
2、DiscardPolicy
当任务添加到线程池中被拒绝时,默认情况下它将丢弃被拒绝的任务。(即该策略下,直接丢弃任务,什么都不做)
3.DiscardoldestPolicy
当任务添加到线程池中被拒绝时,线程池会放弃等待队列中最旧的未处理任务,然后将被拒绝的任务添加到等待队列中。(该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列)
4、CallerRunsPolicy
不进入线程池执行,在这种方式(CallerRunsPolicy)中,任务将由调用者线程去执行。(用于被拒绝任务的处理程序,它直接在execute方法的调用线程中运行被拒绝的任务:如果执行程序已关闭,则会丢弃该任务。)
2.Future和CompletableFuture的用法
在Java多线程编程中,Future是一个接口,用于表示一个异步计算的结果。当启动callable线程时,就可以声明一个Future,用于接收返回结果。它提供了一系列的方法,用于管理和获取任务的执行结果。Future接口定义在java.util.concurrent包中。
使用Future可以将任务提交给线程池执行,并在需要时获取任务的执行结果。它的主要作用是允许主线程在提交异步任务后,继续执行其他操作,而不需要等待任务执行完成。
下面是Future接口的一些常用方法:
1、boolean cancel(boolean mayInterruptIfRunning): 取消任务的执行。如果任务正在执行,并且2、mayInterruptIfRunning参数设置为true,则会尝试中断任务的执行。
3、boolean isCancelled(): 判断任务是否已被取消。
4、boolean isDone(): 判断任务是否已经完成。
5、V get() throws InterruptedException, ExecutionException: 获取任务的执行结果。如果任务还未执行完毕,get()方法会阻塞直到任务执行完成。如果任务执行过程中发生异常,get()方法会抛出ExecutionException,可以通过getCause()方法获取具体的异常信息。
6、V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException: 在指定的时间内获取任务的执行结果,如果任务在指定时间内未执行完毕,会抛出TimeoutException异常。
通过Future接口,我们可以方便地提交任务并获取任务执行结果。这对于需要处理耗时操作的应用程序非常有用,可以提高系统的并发性和响应性。
栗子:四个刚需(线程)去买房摇号,future获取摇号结果。摇号结果未出,就一直阻塞。
public class FutureTest {
/**
* 买房摇号
*/
public static class Yaohao implements Callable<Integer> {
/**
* 返回摇号结果
* @return 0:中签 1:没中
* @throws Exception
*/
@Override
public Integer call() throws Exception {
Random random = new Random();
//模拟摇号,10天内出结果
TimeUnit.SECONDS.sleep(random.nextInt(10));
int result = random.nextInt(2);
System.out.println(" "+Thread.currentThread().getName()+" is done!");
return result;
}
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
Yaohao gangxu1 = new Yaohao();
Yaohao gangxu2 = new Yaohao();
Yaohao gangxu3 = new Yaohao();
Yaohao gangxu4 = new Yaohao();
ExecutorService es = Executors.newCachedThreadPool();
Future<Integer> result1 = es.submit(gangxu1);
Future<Integer> result2 = es.submit(gangxu2);
Future<Integer> result3 = es.submit(gangxu3);
Future<Integer> result4 = es.submit(gangxu4);
es.shutdown();
System.out.println("刚需1,摇号结果:"+(result1.get()==1?"中签":"没中"));
System.out.println("刚需2,摇号结果:"+(result2.get()==1?"中签":"没中"));
System.out.println("刚需3,摇号结果:"+(result3.get()==1?"中签":"没中"));
System.out.println("刚需4,摇号结果:"+(result4.get()==1?"中签":"没中"));
}
}
两个例子
public class FutureTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
UserInfoService userInfoService = new UserInfoService();
MedalService medalService = new MedalService();
long userId =666L;
long startTime = System.currentTimeMillis();
//调用用户服务获取用户基本信息
FutureTask<UserInfo> userInfoFutureTask = new FutureTask<>(new Callable<UserInfo>() {
@Override
public UserInfo call() throws Exception {
return userInfoService.getUserInfo(userId);
}
});
executorService.submit(userInfoFutureTask);
Thread.sleep(300); //模拟主线程其它操作耗时
FutureTask<MedalInfo> medalInfoFutureTask = new FutureTask<>(new Callable<MedalInfo>() {
@Override
public MedalInfo call() throws Exception {
return medalService.getMedalInfo(userId);
}
});
executorService.submit(medalInfoFutureTask);
UserInfo userInfo = userInfoFutureTask.get();//获取个人信息结果
MedalInfo medalInfo = medalInfoFutureTask.get();//获取勋章信息结果
System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");
}
}
FutureTask和Future的区别
1、功能不同:Future是一个接口,用于表示一个异步计算的结果,可以用来取消任务、查询结果是否完成以及获取计算结果。FutureTask是一个实现了Future接口的具体类,同时也是一个可执行的任务,可以被Executor执行。
2、使用方式不同:Future通常与ExecutorService一起使用,用来提交任务并获取任务的执行结果。FutureTask既可以直接使用FutureTask对象来提交任务,也可以将其作为Runnable或Callable对象提交给ExecutorService。
3、创建方式不同:创建Future对象时,通常使用ExecutorService提交任务后返回的Future实例。而创建FutureTask对象时,可以直接通过new FutureTask(callable)构造函数或new FutureTask(runnable, result)构造函数来实例化。
4、取消任务的能力不同:Future接口提供了cancel(boolean mayInterruptIfRunning)方法,用于取消任务的执行。而FutureTask类还提供了cancel(boolean mayInterruptIfRunning)方法以及boolean cancel(boolean mayInterruptIfRunning)方法,用于取消任务的执行并返回取消成功与否的信息。
总的来说,Future是一个接口,用于表示一个异步计算的结果;FutureTask是Future接口的具体实现类,同时也是一个可执行的任务。FutureTask相比Future更为灵活,能够直接作为任务提交给ExecutorService执行,并提供了更多的取消任务的方法。
ExecutorService 的submit方法的作用
ExecutorService 是 Java 提供的一个用于管理线程池的接口。它可以用来执行异步任务并管理线程的生命周期。submit 方法是 ExecutorService 接口定义的一个方法,用于向线程池提交一个任务并获取一个 Future 对象来表示任务的执行结果。
具体来说,submit 方法用于提交一个 Callable 或 Runnable 对象到线程池中执行。Callable 是带有返回结果的任务,而 Runnable 是没有返回结果的任务。submit 方法将任务提交给线程池后会立即返回一个 Future 对象,通过这个对象可以获得任务的执行结果或者取消任务的执行。
使用 submit 方法可以方便地管理线程池中的任务,可以通过 Future 对象获取任务的状态、结果或者取消任务的执行。通常情况下,我们可以使用 submit 方法来替代 execute 方法,因为它具有更多的功能和灵活性。
submit 和execute 的区别
submit() 和 execute() 是 ExecutorService 接口用于提交任务到线程池的两种方法,它们之间有以下区别:
1、返回值类型不同: submit() 方法返回一个 Future 对象,可以用来获取任务的执行结果或者取消任务的执行;而 execute() 方法没有返回值,无法获取任务的执行结果。
2、异常处理不同: submit() 方法能够捕获任务执行过程中抛出的异常,将异常封装到 Future 对象中,通过调用 get() 方法或者 get(long, TimeUnit) 方法获取任务的执行结果时,如果任务抛出了异常,可以通过 ExecutionException 来获取异常信息;而 execute() 方法无法对任务抛出的异常进行处理,如果任务抛出了异常,线程池会将异常记录到控制台。
3、参数类型不同: submit() 方法接受 Callable 或者 Runnable 对象作为参数,Callable 是带有返回值的任务,而 Runnable 是没有返回值的任务;而 execute() 方法只接受 Runnable 对象作为参数。因此,如果需要获取任务执行的结果,应该使用 submit() 方法,如果不需要获取结果,可以使用 execute() 方法。
总的来说,submit() 方法比 execute() 方法更加灵活,可以获取任务的执行结果和捕获任务中抛出的异常,因此在处理需要获取结果或者处理异常的任务时,推荐使用 submit() 方法。而对于不需要获取结果的简单任务,可以使用 execute() 方法。
Future的局限
Future对于结果的获取,不是很友好,只能通过阻塞或者轮询的方式得到任务的结果。
Future.get() 就是阻塞调用,在线程获取结果之前get方法会一直阻塞。Future提供了一个isDone方法,可以在程序中轮询这个方法查询执行结果。
阻塞的方式和异步编程的设计理念相违背,而轮询的方式会耗费无谓的CPU资源。因此,JDK8设计出CompletableFuture,提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方。
CompletableFuture 使用详解(重要)
参考:https://www.jianshu.com/p/6bac52527ca4
https://blog.csdn.net/sermonlizhi/article/details/123356877
https://juejin.cn/post/6970558076642394142
CompletableFuture提供了多种方法来获取异步计算的结果。以下是一些常用的方法:
使用get()方法:CompletableFuture类继承了Future接口,因此可以使用get()方法来获取计算的结果。但需要注意的是,get()方法是阻塞的,会等待异步计算完成后返回结果。如果计算没有完成,get()方法会一直阻塞,直到计算完成并返回结果或者抛出异常。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");
String result = future.get(); // 阻塞获取计算结果
使用join()方法:与get()方法类似,join()方法也可以获取计算结果,但它是非阻塞的。如果计算没有完成,join()方法会等待计算完成后立即返回结果。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");
String result = future.join(); // 非阻塞获取计算结果
使用CompletableFuture组合方法:CompletableFuture提供了一系列方法,如thenApply、thenAccept、thenCompose等,用于对计算结果进行处理。这些方法返回的是新的CompletableFuture实例,可以继续链式调用。通过这些方法,我们可以在异步计算完成后,对结果进行进一步的操作或处理。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(s -> s + " World")
.thenApply(String::toUpperCase);
String result = future.get();
使用回调方法:CompletableFuture还提供了一系列回调方法,如whenComplete、handle、thenAccept、exceptionally等,用于在计算完成后执行相应的操作或处理。这些方法允许我们以非阻塞的方式处理计算结果,并在计算完成或出现异常时执行相应的逻辑。
CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(s -> s + " World")
.thenAccept(System.out::println)
.exceptionally(ex -> {
System.out.println("Error: " + ex.getMessage());
return null;
});
案例:
1、CompletableFuture默认使用的线程池是 ForkJoinPool.commonPool(),commonPool是当前 JVM(进程) 上的所有 CompletableFuture、并行 Stream 共享的,commonPool 的目标场景是非阻塞的 CPU 密集型任务,其线程数默认为 CPU 数量减1,所以对于我们用java常做的IO密集型任务,默认线程池是远远不够使用的
2、CompletableFuture是否使用默认线程池的依据,和机器的CPU核心数有关。当CPU核心数-1>1时,才会使用默认的线程池,否则将会为每个CompletableFuture的任务创建一个新线程去执行。
也就是说,CompletableFuture的默认线程池,只有在双核以上的机器内才会使用。在双核及以下的机器中,会为每个任务创建一个新线程,等于没有使用线程池,且有资源耗尽的风险。
原文链接:https://blog.csdn.net/IndexMan/article/details/134720625
在使用CompletableFuture时,务必要自定义线程池。
@Configuration
public class ThreadPoolConfig {
//参数初始化
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
//核心线程数量大小
private static final int corePoolSize = Math.max(2, Math.min(CPU_COUNT-1,4));
//线程池最大容纳线程数
private static final int maxPoolSize = CPU_COUNT * 2 + 1;
//阻塞队列
private static final int workQueue = 20;
//线程空闲后的存活时长
private static final int keepAliveTime = 30;
@Bean("asyncTaskExecutor")
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
//核心线程数
threadPoolTaskExecutor.setCorePoolSize(corePoolSize);
//最大线程数
threadPoolTaskExecutor.setMaxPoolSize(maxPoolSize);
//等待队列
threadPoolTaskExecutor.setQueueCapacity(workQueue);
//线程前缀
threadPoolTaskExecutor.setThreadNamePrefix("asyncTaskExecutor-");
//线程池维护线程所允许的空闲时间,单位为秒
threadPoolTaskExecutor.setKeepAliveSeconds(keepAliveTime);
// 线程池对拒绝任务(无线程可用)的处理策略
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
}
}
在CompletableFuture中指定自定义线程池
@RestController
@RequestMapping("/task")
public class CompletableTaskController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
@Qualifier("asyncTaskExecutor")
private Executor asyncTaskExecutor;
@RequestMapping("testOrderTask")
public String testOrderTask(){
List<CompletableFuture<List<Integer>>> futureList = Lists.newArrayList();
// 任务1,计算3秒
CompletableFuture<List<Integer>> task1 = CompletableFuture.supplyAsync(() -> {
sleepSeconds(3L);
return Lists.newArrayList(1,2,3);
}, asyncTaskExecutor);
futureList.add(task1);
// 任务2,计算2秒,得答案5
CompletableFuture<List<Integer>> task2 = CompletableFuture.supplyAsync(() -> {
sleepSeconds(1L);
return Lists.newArrayList(4,5,6);
}, asyncTaskExecutor);
futureList.add(task2);
// 任务3,计算3秒,得答案5
CompletableFuture<List<Integer>> task3 = CompletableFuture.supplyAsync(() -> {
sleepSeconds(2L);
return Lists.newArrayList(7,8,9);
}, asyncTaskExecutor);
futureList.add(task3);
// 写法1
List<Integer> newList = futureList.stream().map(CompletableFuture::join).flatMap(List::stream).collect(Collectors.toList());
return JSON.toJSONString(newList);
}
private void sleepSeconds(long seconds) {
try {
Thread.sleep(seconds * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
设置CompletableFuture设置异步超时的方式
下面举例一个常见场景。
假如我们有两个 RPC 远程调用服务,我们需要获取两个 RPC 的结果后,再进行后续逻辑处理。
public static void main(String[] args) {
// 任务 A,耗时 2 秒
int resultA = compute(1);
// 任务 B,耗时 2 秒
int resultB = compute(2);
// 后续业务逻辑处理
System.out.println(resultA + resultB);
}
可以预估到,串行执行最少耗时 4 秒,并且 B 任务并不依赖 A 任务结果。
对于这种场景,我们通常会选择并行的方式优化,Demo 代码如下:
public static void main(String[] args) {
// 仅简单举例,在生产代码中可别这么写!
// 统计耗时的函数
time(() -> {
CompletableFuture<Integer> result = Stream.of(1, 2)
// 创建异步任务
.map(x -> CompletableFuture.supplyAsync(() -> compute(x), executor))
// 聚合
.reduce(CompletableFuture.completedFuture(0), (x, y) -> x.thenCombineAsync(y, Integer::sum, executor));
// 等待结果
try {
System.out.println("结果:" + result.get());
} catch (ExecutionException | InterruptedException e) {
System.err.println("任务执行异常");
}
});
}
输出:
[async-1]: 任务执行开始:1
[async-2]: 任务执行开始:2
[async-1]: 任务执行完成:1
[async-2]: 任务执行完成:2
结果:3
耗时:2 秒
看上去 CompletableFuture 现有功能可以满足我们诉求。但当我们引入一些现实常见情况时,一些潜在的不足便暴露出来了。
compute(x) 如果是一个根据入参查询用户某类型优惠券列表的任务,我们需要查询两种优惠券并组合在一起返回给上游。假如上游要求我们 2 秒内处理完毕并返回结果,但 compute(x) 耗时却在 0.5 秒 ~ 无穷大波动。这时候我们就需要把耗时过长的 compute(x) 任务结果放弃,仅处理在指定时间内完成的任务,尽可能保证服务可用。
那么以上代码的耗时由耗时最长的服务决定,无法满足现有诉求。通常我们会使用 get(long timeout, TimeUnit unit) 来指定获取结果的超时时间,并且我们会给 compute(x) 设置一个超时时间,达到后自动抛异常来中断任务。
解决方法
在 JDK 9,CompletableFuture 正式提供了 orTimeout、completeTimeout 方法,来准确实现异步超时控制
public CompletableFuture<T> orTimeout(long timeout, TimeUnit unit) {
if (unit == null)
throw new NullPointerException();
if (result == null)
whenComplete(new Canceller(Delayer.delay(new Timeout(this), timeout, unit)));
return this;
}
JDK 9 orTimeout 其实现原理是通过一个定时任务,在给定时间之后抛出异常。如果任务在指定时间内完成,则取消抛异常的操作。
以上代码我们按执行顺序来看下:
首先执行 new Timeout(this)。
static final class Timeout implements Runnable {
final CompletableFuture<?> f;
Timeout(CompletableFuture<?> f) { this.f = f; }
public void run() {
if (f != null && !f.isDone())
// 抛出超时异常
f.completeExceptionally(new TimeoutException());
}
}
通过源码可以看到,Timeout 是一个实现 Runnable 的类,run() 方法负责给传入的异步任务通过 completeExceptionally CAS 赋值异常,将任务标记为异常完成。
那么谁来触发这个 run() 方法呢?我们看下 Delayer 的实现。
static final class Delayer {
static ScheduledFuture<?> delay(Runnable command, long delay,
TimeUnit unit) {
// 到时间触发 command 任务
return delayer.schedule(command, delay, unit);
}
static final class DaemonThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
t.setName("CompletableFutureDelayScheduler");
return t;
}
}
static final ScheduledThreadPoolExecutor delayer;
static {
(delayer = new ScheduledThreadPoolExecutor(
1, new DaemonThreadFactory())).
setRemoveOnCancelPolicy(true);
}
}
Delayer 其实就是一个单例定时调度器,Delayer.delay(new Timeout(this), timeout, unit) 通过 ScheduledThreadPoolExecutor 实现指定时间后触发 Timeout 的 run() 方法。
到这里就已经实现了超时抛出异常的操作。但当任务完成时,就没必要触发 Timeout 了。因此我们还需要实现一个取消逻辑。
static final class Canceller implements BiConsumer<Object, Throwable> {
final Future<?> f;
Canceller(Future<?> f) { this.f = f; }
public void accept(Object ignore, Throwable ex) {
if (ex == null && f != null && !f.isDone())
// 3 未触发抛异常任务则取消
f.cancel(false);
}
}
当任务执行完成,或者任务执行异常时,我们也就没必要抛出超时异常了。因此我们可以把 delayer.schedule(command, delay, unit) 返回的定时超时任务取消,不再触发 Timeout。 当我们的异步任务完成,并且定时超时任务未完成的时候,就是我们取消的时机。因此我们可以通过 whenComplete(BiConsumer<? super T, ? super Throwable> action) 来完成。
Canceller 就是一个 BiConsumer 的实现。其持有了 delayer.schedule(command, delay, unit) 返回的定时超时任务,accept(Object ignore, Throwable ex) 实现了定时超时任务未完成后,执行 cancel(boolean mayInterruptIfRunning) 取消任务的操作。
jdk8如何实现
以下是自己的工具类以及用法
调用方式:
CompletableFutureExpandUtils.orTimeout(异步任务, 超时时间, 时间单位);
工具类:
package com.jd.jr.market.reduction.util;
import com.jdpay.market.common.exception.UncheckedException;
import java.util.concurrent.*;
import java.util.function.BiConsumer;
/**
* CompletableFuture 扩展工具
*
* @author zhangtianci7
*/
public class CompletableFutureExpandUtils {
/**
* 如果在给定超时之前未完成,则异常完成此 CompletableFuture 并抛出 {@link TimeoutException} 。
*
* @param timeout 在出现 TimeoutException 异常完成之前等待多长时间,以 {@code unit} 为单位
* @param unit 一个 {@link TimeUnit},结合 {@code timeout} 参数,表示给定粒度单位的持续时间
* @return 入参的 CompletableFuture
*/
public static <T> CompletableFuture<T> orTimeout(CompletableFuture<T> future, long timeout, TimeUnit unit) {
if (null == unit) {
throw new UncheckedException("时间的给定粒度不能为空");
}
if (null == future) {
throw new UncheckedException("异步任务不能为空");
}
if (future.isDone()) {
return future;
}
return future.whenComplete(new Canceller(Delayer.delay(new Timeout(future), timeout, unit)));
}
/**
* 超时时异常完成的操作
*/
static final class Timeout implements Runnable {
final CompletableFuture<?> future;
Timeout(CompletableFuture<?> future) {
this.future = future;
}
public void run() {
if (null != future && !future.isDone()) {
future.completeExceptionally(new TimeoutException());
}
}
}
/**
* 取消不需要的超时的操作
*/
static final class Canceller implements BiConsumer<Object, Throwable> {
final Future<?> future;
Canceller(Future<?> future) {
this.future = future;
}
public void accept(Object ignore, Throwable ex) {
if (null == ex && null != future && !future.isDone()) {
future.cancel(false);
}
}
}
/**
* 单例延迟调度器,仅用于启动和取消任务,一个线程就足够
*/
static final class Delayer {
static ScheduledFuture<?> delay(Runnable command, long delay, TimeUnit unit) {
return delayer.schedule(command, delay, unit);
}
static final class DaemonThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
t.setName("CompletableFutureExpandUtilsDelayScheduler");
return t;
}
}
static final ScheduledThreadPoolExecutor delayer;
static {
delayer = new ScheduledThreadPoolExecutor(1, new DaemonThreadFactory());
delayer.setRemoveOnCancelPolicy(true);
}
}
}