异步线程池

异步&线程池

为了节约时间和提高吞吐量,我们要做一些异步请求

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

​ 1,继承Thread

​ 2,实现Runnable接口

​ 3,实现Callable接口+FutureTask(可以拿到返回结果,可以处理异常)

​ 4,线程池

方式1和方式2:主线程无法获取线程的运算结果。不适合当前场景

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

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

Executors.newFiexedThreadPool(3);
//或者
new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,TimeUnit,unit,workQueue,threadFactory,handler)

通过线程池性能稳定,也可以获取执行结果,并捕获异常。但是,在业务复杂情况下,一个异步调用可能会依赖于另一个异步调用的执行结果。

方式一实现代码如下:

1,继承Thread
public class ThreadTest {
   
    public static void main(String[] args) {
   
           System.out.println("main....start....");
           Thread01 thread01 = new Thread01();
           thread01.start();//启动线程
           System.out.println("main....end....");
        }
    public static class Thread01 extends Thread{
   
        @Override
        public void run() {
   
            System.out.println("当前线程: "+Thread.currentThread().getId());
            int i = 10/2;
            System.out.println("运行结果: "+i);
        }
    }
}
main....start....
main....end....
当前线程:12
运行结果:5

方式二实现代码如下

2,实现Runnable接口
       public static class Runable01 implements Runnable{
        @Override
        public void run() {
            System.out.println("当前线程: "+Thread.currentThread().getId());
            int i = 10/2;
            System.out.println("运行结果: "+i);
        }
    }

main方法中
        System.out.println("main....start....");
       	Runable01 runable01 = new Runable01();
        new Thread(runable01).start();
        System.out.println("main....end....");
main....start....
main....end....
当前线程:12
运行结果:5

第三种方式

public static class Callable01 implements Callable{
   
        @Override
        public Object call() throws Exception {
   
            System.out.println("当前线程: "+Thread.currentThread().getId());
            int i = 10/2;
            System.out.println("运行结果: "+i);
            return i;
        }
    }

main方法中
    FutureTask<Integer> futureTask = new FutureTask<>(new Callable01());
    new Thread(futureTask).start();
System.out.println("main....end....");

可以看到Callable接口中直接结束了

image-20220505224214959

所以我们将采取FutureTask的方式来启动Callable的线程,根据FutureTask源码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Yl5js78-1651988685321)(C:\Users\靓仔在此\AppData\Roaming\Typora\typora-user-images\image-20220505224311186.png)]

继而new出来返回FutureTask,FutureTask又继承RunnableFuture,而RunnableFuture又分别继承Runnable,Future

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bmbfbJQB-1651988685322)(C:\Users\靓仔在此\AppData\Roaming\Typora\typora-user-images\image-20220505224429176.png)]

所以它的启动方式就是FutureTask futureTask = new FutureTask<>(new Callable01());new Thread(futureTask).start();

运行结果如下

main....start....
main....end....
当前线程:12
运行结果:5

Callable最大的好处是FutureTask 我们假设后台运行的很慢,我们还希望后台返回这个Callable的计算结果,就可以使用FutureTask ,它有一个方法.get

等异步方法执行完,返回它的结果

Integer integer = futureTask.get();
//等待整个线程执行完成,获取返回结果


修改main方法
    Integer integer = futureTask.get();
System.out.println("main....end...."+integer);

	main....start....
当前线程:12
运行结果:5
main....end....+5

所以Integer integer = futureTask.get();是一个阻塞等待

futureTask不仅可以接受Callable,还接受Runnable,如果是Runnable,还可以传一个对象,让这个对象接受参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CjrG64pF-1651988685322)(C:\Users\靓仔在此\AppData\Roaming\Typora\typora-user-images\image-20220505225643754.png)]

第四种方式

给线程池直接提交任务。

为什么使用线程池?如果我们后来的项目中异步比较多,每次都进行原生代码new Thread().start(),没执行一个线程就开启一个start(存在问题)

我们以后在业务代码里,以上三种启动线程的方式都不能用 避免大量new Thread造成资源消耗完,应该将所有的多线程异步任务交给线程池执行

JUC中最快得到线程池

ExecutorService executorService = Executors.newFixedThreadPool(10);
//10代表有十个空闲线程   当前系统中池只有一两个,每个异步任务,提交给线程让他自己去执行就行

并不应该是每运行一次就创建一个线程池,应该是一个线程池,大家将所有任务都交给这个线程池来处理

要想访问线程池

public static ExecutorService executorService = Executors.newFixedThreadPool(10);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uR0WvJA8-1651988685323)(C:\Users\靓仔在此\AppData\Roaming\Typora\typora-user-images\image-20220505231118555.png)]

里面有submit,可以将一个Runnable或Callable的任务,或者使用execute让它执行

submit跟execute两个区别

如果是submit给线程池中添加一个任务,我们可以获取任务的返回值;如果是execute,直接让线程池执行我们这个操作,返回类型是void 都是给线程池提交异步请求

main中执行
 executorService.execute(new Runable01());
main....start....
main....end....
当前线程:12
运行结果:5

这是我们将任务提交给线程池,而不是我们自己在new

 给线程池直接提交任务。
         * executorService.execute(new Runable01());
         * 1,创建
         *   1)Executors
         *   2)new ThreadPoolExecutor();
         *   Future:可以获得到异步结果
         *
         * 区别
         * 1,2不能获取返回值3可以获取返回值
         * 1,2,3都不能控制资源
         * 4,可以控制资源,性能稳定
         *
         */

2,线程池的七大参数

/**
         * int corePoolSize【5】, 我们的线程数量总是在池中保持着  就算系统当前是空闲的,也是在池总一直有的,等待接收新任务的 除非设置allowCoreThreadTimeOut(允许核心线程超时,这样核心线程空闲就被回收了,否则就不回收)
         * 核心线程数[一直存在除非设置allowCoreThreadTimeOut]  线程池  创建好以后准备就绪的线程数量,就等待来接受异步任务去执行  5个 Thread thread = new Thread()  thread.start()
         int maximumPoolSize, 相当于线程池最大数量;控制资源并发
         long keepAliveTime,存活时间  如果当前我们线程数量大于核心线程数的时候  释放空闲线程(maximumPoolSize-corePoolSize)  只要线程空闲大于指定keepAliveTime
         TimeUnit unit, 时间单位
         BlockingQueue<Runnable> workQueue,阻塞队列  如果任务有很多,就会将目前多的任务放在队列里面   只要有线程空闲,就会去队列里面取出新的任务继续执行
         ThreadFactory threadFactory, 线程的创建工厂
         RejectedExecutionHandler handler  如果队列满了,按照我们指定的拒绝策略拒绝执行任务(默认使用丢弃策略AbortPolicy)

         工作顺序:
         1)线程池创建好,准备好core数量的核心线程,准备接受任务
         1.1core满了,就将再进来的任务放到阻塞队列中,空闲的core就会自己去阻塞队列获取任务执行
         1.2阻塞队列满了,就直接开新线程执行,最大只能开到max指定的数量
         1.3max满了就用拒绝策略RejectedExecutionHandler拒绝任务
         1.4max都执行完成,有很多空闲  在指定的时间【keepAliveTime】以后,释放max-core这些线程

         new LinkedBlockingDeque<>():默认是Integer的最大值。内存不够

         */
  ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,
                200,
                10,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(100000),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
                );

默认ThreadFactory

image-20220507215612157

拒绝策略(默认使用丢弃策略AbortPolicy)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ajzu2oTx-1651988685323)(C:\Users\靓仔在此\AppData\Roaming\Typora\typora-user-images\image-20220507220030883.png)]

一个简单面试题:

一个线程池core7 max20 queue 50 100并发进来怎么分配
首先 7个会立即得到执行 50个进入队列 再开13个进行执行 剩下30个采用拒绝策略
如果不想抛弃还要执行 CallerRunsPolicy;同步方法   也可以尝试使用抛弃以前老数据

3,Executors中常见四种线程池

  • newCachedThreadPool() core是0,所有都可以回收

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZDdVdRGS-1651988685324)(C:\Users\靓仔在此\AppData\Roaming\Typora\typora-user-images\image-20220507223002518.png)]

    创建一个可缓存线程池,如果线程池长度超过处理需求,可灵活回收空闲线程,若无可回收,则新建线程

  • newFixedThreadPool() 固定大小 core=max 都不可被回收

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tF5seZZV-1651988685324)(C:\Users\靓仔在此\AppData\Roaming\Typora\typora-user-images\image-20220507224436089.png)]

    创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待

  • newScheduledThreadPool() 定时任务线程池

    在这里插入图片描述

    创建一个定长线程池,支持定时及周期性任务执行

  • newSingleThreadExecutor() 单线程的线程池,后台从队列里面获取任务 挨个执行

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WUipQRDK-1651988685325)(C:\Users\靓仔在此\AppData\Roaming\Typora\typora-user-images\image-20220507224810789.png)]

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

    • 降低资源的消耗

      • 通过重复利用已经创建好的线程降低线程的创建和销毁带来的损耗
    • 提高响应速度

      • 因为线程池中的线程数没有超过线程池的最大上限时,有的线程处于等待分配任务的状态,当任务来时无需创建新的线程就能执行
    • 提高线程的可管理性

      • 线程池会根据当前系统特点对池内的线程进行优化处理,减少创建和销毁线程带来的系统开销。无限的创建和销毁不仅会消耗系统资源,还降低系统的稳定性,使用线程池进行统一分配。

CompletableFuture异步编排

业务场景:

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hBfyMzBf-1651988685326)(C:\Users\靓仔在此\AppData\Roaming\Typora\typora-user-images\image-20220507231407566.png)]

假如商品详细页的每个查询,需要如下标注的时间才能完成

那么,用户需要5.5s才能看到商品详细页的内容。很显然是不能接受的

如果多个线程同时完成这6步,也许就只需要1.5s就完成响应

Future是Java5添加的类,用来描述一个异步计算的结果,你可以使用’isDone‘方法检查计算是否完成,或者使用’get‘阻塞猪调用线程,直到计算完成返回结果,你也可以使用’cancel‘方法停止任务执行

虽然Future以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不 方便,只能通过阻塞或者轮询的方式得到任务的结果。阻塞的方式显然和我们的异步编程的 初衷相违背,轮询的方式又会耗费无谓的 CPU 资源,而且也不能及时地得到计算结果,为 什么不能用观察者设计模式当计算结果完成及时通知监听者呢? 很多语言,比如 Node.js,采用回调的方式实现异步编程。Java 的一些框架,比如 Netty,自 己扩展了 Java 的 Future接口,提供了addListener等多个扩展方法;Google guava 也提供了 通用的扩展 Future;Scala 也提供了简单易用且功能强大的 Future/Promise 异步编程模式。 作为正统的 Java 类库,是不是应该做点什么,加强一下自身库的功能呢? 在 Java 8 中, 新增加了一个包含 50 个方法左右的类: CompletableFuture,提供了非常强大的 Future 的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以 通过回调的方式处理计算结果,并且提供了转换和组合 CompletableFuture 的方法。 CompletableFuture 类实现了 Future 接口,所以你还是可以像以前一样通过get方法阻塞或 者轮询的方式获得结果,但是这种方式不推荐使用

CompletableFuture 和 FutureTask 同属于 Future 接口的实现类,都可以获取线程的执行结果。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qUka626q-1651988685326)(C:\Users\靓仔在此\AppData\Roaming\Typora\typora-user-images\image-20220508104442345.png)]

CompletableFuture是jdk1.8以后添加的功能 CompletableFuture就类似于vue中promise的ajax请求,执行完了就.then

CompletableFuture提供了四个静态方法来创建一个异步操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Im8GqjtU-1651988685326)(C:\Users\靓仔在此\AppData\Roaming\Typora\typora-user-images\image-20220507233226681.png)]

1创建异步对象

 public static ExecutorService executor = Executors.newFixedThreadPool(10);

    public static void main(String[] args) {
   
        System.out.println("main....start....");
        CompletableFuture<Void></
  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值