首页 > 编程语言 >Java线程池以及Future和CompletableFuture的用法

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

时间:2024-06-12 18:22:05浏览次数:30  
标签:Java get submit 任务 Future 线程 方法

参考:https://blog.csdn.net/weixin_50330544/article/details/131687150

1.线程池

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

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

线程池所在包java.util.concurrent
顶级接口Executor,真正的线程池接口是ExecutorService
Executors类提供创建线程池的方法
image
Java内置的线程池
image
image
image
自定义线程池
image
image
image
当线程池满了之后就会执行拒绝策略。线程池数量等于最大线程数+阻塞队列数
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
image
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;
        });

标签:Java,get,submit,任务,Future,线程,方法
From: https://www.cnblogs.com/cgy1995/p/18244490

相关文章

  • Java集合总结
    JAVA中常见的集合总结使用集合的好处:可以动态的保存任意多个对象,使用比较方便提供了一些列方便的操作对象的方法:add,remove,set,get等使用集合添加,删除元素的示意代码简洁明了集合主要分为两种:单列集合:集合中存放的是单个对象双列集合:集合中存放的是键值对对象C......
  • 深入理解java设计模式之单例模式
    这里写目录标题概述单例模式是什么单例模式的使用场景单例模式的优缺点单例模式的几种实现方式饿汉式懒汉式双重检查锁定机制静态内部类枚举使用容器几种可能破坏单例类的方法多线程环境下的竞争条件使用反射机制使用序列化多个类加载器......
  • 学习分享-Tomcat 的线程池在工作方式上与普通的 Java 线程池的区别
    前言最近在学习过程中遇到在某个场景下:修改某条数据时,给该线程上分布式写锁,然后引入延迟队列处理其他请求;这个方案有一定的缺点,因为在用到消息队列时,不存在占用过多线程从而导致OOM的问题,消费者组只会安排固定的几个线程去拉取消息,如果碰到上面那种拿不到锁的情况,阻塞等待......
  • SSM-小区物业管理系统-48954(免费领源码+开发文档)可做计算机毕业设计JAVA、PHP、爬虫、
    基于SSM小区物业管理系统摘要随着计算机科学技术日渐成熟,人们已经深刻认识到了计算机功能的强大,计算机已经进入到了人类社会发展的各个领域,并且发挥着十分重要的作用。每个社区的物业管理是一项系统而复杂的工作,它需要一个团队互相配合、分工协作。在该领域,传统的手工存取......
  • java 与jdk 关系
      java与jdk的关系Java与JDK的关系是,Java是一种编程语言,而JDK(JavaDevelopmentKit)是Java语言的官方开发工具包。12JDK是整个Java技术的核心,它包括了JRE(JavaRuntimeEnvironment)和一系列Java开发工具(如javac、java、javadoc等)。JRE是Jav......
  • 【问题解决】java.util.jar.JarException: file:bcprov-jdk18on-1.78.jar is not sign
    现象启动程序报错,同时在classpath下有多个bcprov-jdk开头的包Causedby:java.util.jar.JarException:file:/C:/Users/93986/.gradle/caches/modules-2/files-2.1/org.bouncycastle/bcprov-jdk18on/1.78/619aafb92dc0b4c6cc4cf86c487ca48ee2d67a8e/bcprov-jdk18on-1.78.jaris......
  • 网易面试:SpringBoot如何开启虚拟线程?
    虚拟线程(VirtualThread)也称协程或纤程,是一种轻量级的线程实现,与传统的线程以及操作系统级别的线程(也称为平台线程)相比,它的创建开销更小、资源利用率更高,是Java并发编程领域的一项重要创新。PS:虚拟线程正式发布于Java长期支持版(LongTermSuort,LTS)Java21(也就是JDK21)。......
  • JavaScript常用的流程控制语句
    在JavaScript中,有以下几种常用的流程控制语句:if...else:当if条件为假时,可以使用else语句执行另一段代码。if(condition){//条件为真时执行的代码}else{//条件为假时执行的代码}if...elseif...else:可以链式使用多个if和elseif来根据多个条件执行不......
  • 【S087】Springboot+Thymleaf在线答疑系统项目源码 java源代码
    运行截图:登录学生注册教师注册学生发起问题联系我们后台首页常见问题管理添加常见问题人工答疑学生管理个人信息修改密码项目组成:项目源码:源码获取⬇⬇⬇......
  • 【S086】基于Springboot图书馆管理系统项目源码 java图书借阅管理 含文档
    运行截图:登录后台主页图书列表图书上架借阅图书归还图书用户列表添加用户公告列表发布公告个人信息详情个人信息编辑项目组成:项目源码:项目文档:源码获取⬇⬇⬇......