Java线程池以及Future和CompletableFuture的用法

线程池

是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。

为什么使用线程池?
频繁申请/销毁资源和调度资源,将带来额外的消耗,可能会非常巨大。
对资源无限申请缺少抑制手段,易引发系统资源耗尽的风险。
系统无法合理管理内部的资源分布,会降低系统的稳定性。

使用线程池的好处?
重用存在的线程,减少对象创建、消亡的开销。
有效的控制最大并发数,提高系统资源使用率。
统一的分配、调优和监控、可定时执行、定期执行。

线程池所在包java.util.concurrent
顶级接口Executor,真正的线程池接口是ExecutorService
Executors类提供创建线程池的方法
在这里插入图片描述

线程池工作流程

在这里插入图片描述

Java内置的线程池

禁制使用
在这里插入图片描述
在这里插入图片描述

自定义线程池

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
拒绝任务策略
1、AbortPolicy
当任务添加到线程池中被拒绝时,它将抛出RejectedExecutionException异常。(该策路下,直接丢弃任务,并抛出RejectedExecutionException异常)
2、DiscardPolicy
当任务添加到线程池中被拒绝时,默认情况下它将丢弃被拒绝的任务。(即该策略下,直接丢弃任务,什么都不做)
3.DiscardoldestPolicy
当任务添加到线程池中被拒绝时,线程池会放弃等待队列中最旧的未处理任务,然后将被拒绝的任务添加到等待队列中。(该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列)
4、CallerRunsPolicy
不进入线程池执行,在这种方式(CallerRunsPolicy)中,任务将由调用者线程去执行。(用于被拒绝任务的处理程序,它直接在execute方法的调用线程中运行被拒绝的任务:如果执行程序已关闭,则会丢弃该任务。)

Future和CompletableFuture的用法

在Java多线程编程中,Future是一个接口,用于表示一个异步计算的结果。当启动callable线程时,就可以声明一个Future,用于接收返回结果。它提供了一系列的方法,用于管理和获取任务的执行结果。Future接口定义在java.util.concurrent包中。
使用Future可以将任务提交给线程池执行,并在需要时获取任务的执行结果。它的主要作用是允许主线程在提交异步任务后,继续执行其他操作,而不需要等待任务执行完成。

下面是Future接口的一些常用方法:

  1. boolean cancel(boolean mayInterruptIfRunning): 取消任务的执行。如果任务正在执行,并且mayInterruptIfRunning参数设置为true,则会尝试中断任务的执行。
  2. boolean isCancelled(): 判断任务是否已被取消。
  3. boolean isDone(): 判断任务是否已经完成。
  4. V get() throws InterruptedException, ExecutionException: 获取任务的执行结果。如果任务还未执行完毕,get()方法会阻塞直到任务执行完成。如果任务执行过程中发生异常,get()方法会抛出ExecutionException,可以通过getCause()方法获取具体的异常信息。
  5. 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");
    }
}

FutureTaskFuture的区别

  1. 功能不同:Future是一个接口,用于表示一个异步计算的结果,可以用来取消任务、查询结果是否完成以及获取计算结果。FutureTask是一个实现了Future接口的具体类,同时也是一个可执行的任务,可以被Executor执行。

  2. 使用方式不同:Future通常与ExecutorService一起使用,用来提交任务并获取任务的执行结果。FutureTask既可以直接使用FutureTask对象来提交任务,也可以将其作为RunnableCallable对象提交给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是一个接口,用于表示一个异步计算的结果;FutureTaskFuture接口的具体实现类,同时也是一个可执行的任务。FutureTask相比Future更为灵活,能够直接作为任务提交给ExecutorService执行,并提供了更多的取消任务的方法。

ExecutorService 的submit方法的作用
ExecutorService 是 Java 提供的一个用于管理线程池的接口。它可以用来执行异步任务并管理线程的生命周期。submit 方法是 ExecutorService 接口定义的一个方法,用于向线程池提交一个任务并获取一个 Future 对象来表示任务的执行结果。

具体来说,submit 方法用于提交一个 CallableRunnable 对象到线程池中执行。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 使用详解

CompletableFuture 使用详解
CompletableFuture使用详解
异步编程利器:CompletableFuture详解 |Java 开发实战
CompletableFuture提供了多种方法来获取异步计算的结果。以下是一些常用的方法:

  1. 使用get()方法:CompletableFuture类继承了Future接口,因此可以使用get()方法来获取计算的结果。但需要注意的是,get()方法是阻塞的,会等待异步计算完成后返回结果。如果计算没有完成,get()方法会一直阻塞,直到计算完成并返回结果或者抛出异常。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");
String result = future.get(); // 阻塞获取计算结果
  1. 使用join()方法:与get()方法类似,join()方法也可以获取计算结果,但它是非阻塞的。如果计算没有完成,join()方法会等待计算完成后立即返回结果。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");
String result = future.join(); // 非阻塞获取计算结果
  1. 使用CompletableFuture组合方法:CompletableFuture提供了一系列方法,如thenApply、thenAccept、thenCompose等,用于对计算结果进行处理。这些方法返回的是新的CompletableFuture实例,可以继续链式调用。通过这些方法,我们可以在异步计算完成后,对结果进行进一步的操作或处理。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
        .thenApply(s -> s + " World")
        .thenApply(String::toUpperCase);
String result = future.get();
  1. 使用回调方法: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;
        });

上述是一些常用的获取CompletableFuture计算结果的方法,根据具体需求和场景,你可以选择适合的方式来处理异步计算的结果。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值