异步&线程池

本文详细介绍了Java线程池的初始化方式、四大参数、常见线程池类型及其优缺点,强调了使用线程池在资源管理和响应速度上的优势。此外,文章还探讨了CompletableFuture在异步编程中的应用,包括创建异步对象、任务完成回调、结果处理以及线程串行化等,并举例说明如何通过CompletableFuture提高复杂业务场景下的执行效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一. 线程池

1. 初始化线程的4种方式

  • 方式1 : 继承Tread
  • 方式2 : 实现Runnable接口
  • 方式3 : 实现Callable接口+FutureTask(可以拿到返回结果,可以处理异常)
  • 方式4 : 线程池

  • 方式1和方式2 : 主进程无法获取线程的运算结果

  • 方式3 : 主进程可以获取线程的运算结果,但是不利于控制服务器中的线程资源,可能会导致服务器资源耗尽

  • 方式4 : 通过如下两种方式初始化线程池:

    • Executors.newFiexedThreadPool(3);
      //或者
      new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,TimeUnit,unit,workQueue,threadFactory,handler);
      
  • 通过线程池性能稳定,也可以获取执行结果,并捕获异常,但是,在业务复杂情况下,一个异步调用可能会依赖另一个异步调用的执行结果

2. 线程池的七大参数

  • corePoolSize : 线程池常驻核心线程数,创建线程池后,当有请求任务来之后,就会安排池中线程去执行请求任务,近似理解为今日当值线程.当线程池中的线程数目达到了corePoolSize后,就会把任务放到缓存队列中;
  • maxmumPoolSize : 线程池能够容纳同时执行的最大线程数,此值必须大于等于1
  • keepAliveTime : 多余空闲线程的存活时间.当前线程池的数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止。
  • unit : keepAliveTime的时间单位
  • workQueue : 任务队列,被提交但未被执行的任务
  • threadFactory : 表示生成线程池中线程的线程工厂,用于创建线程,一般用默认的即可
  • handler : 拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maxmumPoolSize)

3. 常见的四种线程池

  • newFixedThreadPool(定长线程池) : 一个有指定的线程数的线程池,有核心的线程,里面有固定的线程数量,响应的速度快.正规的并发线程,多用于服务器.固定的线程数由系统资源设置,核心线程是没有超时机制的,队列大小没有限制,除非线程池关闭了核心线程才会被回收.
  • newCachedThreadPool(可缓冲线程池) : 只有非核心线程,最大线程数很大,每新来一个任务,当没有空余线程的时候就会重新创建一个线程,这边有一个超时机制,当空闲的线程超过60s内没有用到的话,就会被回收,它可以一定程序减少频繁创建/销毁线程,减少系统开销,适用于执行时间短并且数量多的任务场景.
  • ScheduledThreadPool(周期线程池) : 创建一个定长线程池,支持定时及周期性任务执行,通过过schedule方法可以设置任务的周期执行
  • newSingleThreadExecutor(单任务线程池) : 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行,每次任务到来后都会进入阻塞队列,然后按指定顺序执行.

4. 开发中为什么使用线程池

  • 降低资源的消耗 : 通过重复利用已经建好的线程降低线程的创建和销毁带来的损耗
  • 提供响应速度 : 因为线程池中的线程数没有超过线程池的最大上限时,有的线程处于等待分配任务的状态,当任务来时无需创建新的线程就能执行
  • 提供线程的可管理性 : 线程池会根据当前系统特点对池内的线程进行优化处理,减少创建和销毁线程带来的系统开销,无限的创建和销毁线程不仅消耗系统资源,还降低系统的稳定性,使用线程池进行统一分配

二. CompletableFuture异步编排

  • 应用场景: 查询商品详情页的逻辑比较复杂,有些数据还需要远程调用,必然花费更多时间.

    1. 获取sku的基本信息 0.5s
    2. 获取sku的图片信息 0.5s
    3. 获取sku的促销信息 1s
    4. 获取spu的所有销售属性 1s
    5. 获取规格参数组及组下的规格参数 1.5s
    6. spu详情 1s
  • 假如商品详情的每个查询,需要如上标注的时间才能完成,那么,用户需要5.5s之后才能看到商品详情页的内容,很显然是不能接受的,如果有多个线程同时完成这6步操作,也许只需要1.5s即可完成响应

1. 创建异步对象

  • 注: 方法不以Async结尾,意味着Action使用相同的线程执行,而Async会重新把Action提交给线程池,可能会使用其他线程执行(如果使用相同的线程池,也就可能会被同一个线程选择执行)

  • 通过构造方法创建CompletableFuture,然后在线程中将需要得到的结果通过CompletableFuture的complete方法传入,从而监控线程任务的执行状态.

    CompletableFuture<Integer> future = new CompletableFuture<>();//当对象被创建时,任务就开始了
    new Thread(()->{
        System.out.println("当前线程: " + Thread.currentThread().getName());
        int i = 4 / 2;
        future.complete(i);//此方法,以为任务完成,返回结果
        System.out.println("结果: "+i);
    }).start();
    Integer result = future.get();//阻塞方法,直到任务完成.
    System.out.println("返回结果: " + result);
    
  • 如果需要执行一个不需要返回的任务时,就可以使用runAsync,运行异步任务,没有返回

    public static ExecutorService pool = Executors.newFixedThreadPool(10);
    CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {//他持有一个Runnblbe对象,并返回CompletableFuture<Void> ,所以可以链式编程
         System.out.println("当前线程: " + Thread.currentThread().getName());
         int i = 4 / 2;
         System.out.println("结果: "+i);
     },pool);
    
  • 如果需要返回的话,也可以使用supplyAsync

    public static ExecutorService pool = Executors.newFixedThreadPool(10);
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
        System.out.println("当前线程: " + Thread.currentThread().getName());
        int i = 4 / 0;
        System.out.println("结果: " + i);
        return i;
    }, pool);
    Integer result = future.get();//阻塞方法,直到任务完成.
    System.out.println("返回结果: " + result);
    

2.任务完成时,回调方法

  • whenComplete可以处理正常和异常的计算结果,exceptionally处理异常情况

  • whenComplete和whenCompleteAsync的区别:前者是执行当前任务的线程继续执行whenComplete的任务,后者是把whenCompleleAsync这个任务继续提交给线程池进行执行

    public static ExecutorService pool = Executors.newFixedThreadPool(10);
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
        System.out.println("当前线程: " + Thread.currentThread().getName());
        int i = 4 / 0;
        System.out.println("结果: " + i);
        return i;
    }, pool).whenComplete((res, throwable) -> {
        //虽然能得到异常信息,但是无法修改返回数据
        System.out.println("异步任务完成....结果: " + res + "  异常: " + throwable);
    }).exceptionally(throwable -> {
        //可以感知到异常,同时返回默认值
        return 10;
    });
    Integer result = future.get();//阻塞方法,直到任务完成.
    System.out.println("返回结果: " + result);
    

3.handle方法

  • 和whenComplete作用类似,也是回调方法,但是handle方法是可以修改结果的,如果是线程串行化,这个结果可以影响后面的结果

    public static ExecutorService pool = Executors.newFixedThreadPool(10);
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
        System.out.println("当前线程: " + Thread.currentThread().getName());
        int i = 4 / 2;
        System.out.println("结果: " + i);
        return i;
    }, pool).handle((res, throwable) -> {
        System.out.println("异步任务完成....结果: " + res + "  异常: " + throwable);
        if (throwable == null)
            return res;
        return 10;
    });
    Integer result = future.get();//阻塞方法,直到任务完成.
    System.out.println("返回结果: " + result);
    

4.线程串行化方法

  • 串行化: 这个任务的开始或者执行,需要依赖另一个任务的结果,get方法是一个阻塞方法,即这个任务的执行,需要等待另一个任务执行完成
  • thenRun方法: 只要上面的方法执行完成,就开始执行zhenRun,不接收上一步结果,也不返回结果给下一步
  • thenApply方法: 上一个方法执行完成,就开始执行,接收上一步结果,并返回结果给下一步
  • thenAccept方法:…接收结果,不返回结果

5. 聚合

  • ann聚合: 两个任务都要完成,才执行下一个任务
    • thenCombine 有参数 有返回
    • thenAcceptBoth 有参数 无返回
    • runAfterBoth 无参数 无返回
  • or聚合: 两个任务只要有一个完成,就执行下一个任务
    • applyToEither 有参数 有返回
    • acceptEither 有参数 无返回
    • runAfterEither 无参数 无返回
  • 如果聚合多个,可以用CompletableFuture的静态方法allOf和anyOf
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

climb.xu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值