线程池的创建方式,为什么阿里推荐自定义线程池?

3552 篇文章 108 订阅

1、多线程的四种实现方式

多线程的实现方式有四种

  1. 继承thread类

  2. 实现runnable接口

  3. 实现Callable

    • 实现Callable不能简单把Callable对象传给thread,要使用FutureTask做一次封装

    • get()可以获取到call()返回值

    • get()阻塞等待所有线程执行完,才输出

  4. 线程池

通常在业务代码中前三种都不使用,只使用第四种(线程池) 每个系统通常有一两个线程池,每一个异步任务,提交给线程池执行即可

1.1 提交任务到线程池的两种方式

  • execute() 返回值为void,代表只执行异步任务,没有返回值

  • submit() 返回值为Future,既可以执行任务,野口接收返回值。

1.2 四种创建线程方式的区别

  • 继承thread和实现runnable接口的方式,无法得到返回值,实现callable接口可以得到返回值。

  • 前三种方式都无法控制资源,即来一个线程就要创建一个线程,容易使系统资源耗尽

  • 线程池的方式可以控制资源,系统性能稳定。

1.3 开发中为什么使用线程池

  • 降低资源消耗。 可以重复利用已经创建好的线程,降低线程的创建和销毁带来的损耗。

  • 提高响应速度。 因为线程池中的线程都处于等待分配任务状态,当任务过来时可以直接执行。

  • 提高线程的可管理性。 如果是单cpu的话,创建多个线程,会导致资源耗尽,但是线程池有拒绝策略;另外还可以核心业务和非核心业务两种线程池,如果某个时间内存压力大,可以释放掉非核心业务线程池。使用线程池就可以使线程的管理方便。

2 、使用ThreadPoolExecutor方式创建线程池

ThreadPoolExecutor属于原生的创建方式,其他的线程池创建方式都是封装了ThreadPoolExecutor类。

ThreadPoolExecutor继承关系图下图

代码:

public ThreadPoolExecutor(int corePoolSize,
     int maximumPoolSize,
     long keepAliveTime,
     TimeUnit unit,
     BlockingQueue<Runnable> workQueue,
     ThreadFactory threadFactory,
     RejectedExecutionHandler handler) {}

由以上源码知道,当使用ThreadPoolExecutor创建线程时,需要传入七大参数!

3.使用Executors创建线程

Executors创建线程,其底层还是用的是ThreadPoolExecutor创建的。

3.1 Executors创建线程的分类

  • Executors.newFixedThreadPool(10) :固定大小 core = 自定义的线程数,但阻塞队列是无界队列,会OOM内存溢出

  • Executors.newCachedThreadPool(); core是0,最大线程数无限大,无限创建线程的话,会使cpu占用100%,因为cpu要不停的调度线程去执行任务

  • Executors.newSingleThreadExecutor(); 单线程的线程池,后台从队列里取,挨个执行。阻塞队列是无界队列,会OOM内存溢出

  • Executors.newScheduledThreadPool(); 带有定时任务的线程池

3 线程池七大参数

int corePoolSize:核心线程数,线程池创建好就已经准备就绪的线程数 量,等待接收异步任务 ,异步任务进来后,自动执行。核心线程会一直存在,除非设置了allowCoreThreadTimeOut,才允许核心线程超时。

int maximumPoolSize:线程池允许存在的最大线程数

long keepAliveTime:超时时间。如果当前线程数量大于核心数量,且在keepAliveTime时间内保持空闲,就释放掉。 释放的是最大线程数 - 核心线程数

TimeUnit unit: 超时时间单位

BlockingQueue workQueue:阻塞队列,如果线程有很多,就会把线程保存在队列里 只要线程有空闲,就去阻塞队列中取。 分类见下文。

ThreadFactory threadFactory:线程的创建工厂

RejectedExecutionHandler handler:拒绝策略 ,如果线程满了采取的策略

拒绝策略分类:

  • DiscardOldestPolicy 抛弃掉最早进入的线程

  • DiscardPolicy 抛弃掉最新的线程

  • DiscardPolicy 剩余的线程调用run方法,变为同步执行

  • DiscardPolicy 抛弃掉最新的线程,并抛出异常!

4 线程池的工作顺序

工作顺序

  1. 线程池创建,准备好核心线程core数,准备接受任务

  2. 核心线程core满了,就将再进来的任务放入阻塞队列中,空闲的core就回去阻塞队列中获取任务执行

  3. 阻塞队列满了,就直接开新线程,最大只能开到max执行的数量

  4. max线程数满了,采用拒绝策略拒绝新来的任务

  5. 当任务量减轻,max - core剩余的线程在keepAliveTime时间后,释放掉内存

5. 阻塞队列

5.1 为什么要使用阻塞队列?

我们知道队列是先进先出的。当放入一个元素的时候,会放在队列的末尾,取出元素的时候,会从队头取。那么,当队列为空或者队列满的时候怎么办呢。

这时,阻塞队列,会自动帮我们处理这种情况。

当阻塞队列为空的时候,从队列中取元素的操作就会被阻塞。当阻塞队列满的时候,往队列中放入元素的操作就会被阻塞。

而后,一旦空队列有数据了,或者满队列有空余位置时,被阻塞的线程就会被自动唤醒。

这就是阻塞队列的好处,你不需要关心线程何时被阻塞,也不需要关心线程何时被唤醒,一切都由阻塞队列自动帮我们完成。我们只需要关注具体的业务逻辑就可以了。

5.2 阻塞队列的分类

https://blog.csdn.net/wcc178399/article/details/106663678

6、CompletableFuture异步编排

应用场景:

电商项目中,获取商品详情通常业务比较复杂,还需要调用别的服务的接口,比较耗时。假如每个数据的获取时间如下图所示,如果不使用异步,用户需要5.5s后才能看到页面内容,显然是不合理的。如果使用异步,同时有6个线程同时获取这些数据,用户只需要1.5s即可看到!

CompletableFuture实现了Future接口,FutureTask也实现了Future接口

6.1 单任务异步

 //自定义一个线程池
 ExecutorService service = Executors.newFixedThreadPool(10);
 //  CompletableFuture的使用
 
 //1.无返回值的异步任务 runAsync()
 CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
     System.out.println("线程号为***" + Thread.currentThread().getId());
     int i = 5;
     System.out.println("---------" + i);
 }, service);

 //2.有返回值异步任务 supplyAsync()
 CompletableFuture<Integer> integerCompletableFuture = CompletableFuture.supplyAsync(() -> {
     System.out.println("线程号为***" + Thread.currentThread().getId());
     int i = 5;
     System.out.println("---------" + i);
     return i;
 }, service).whenComplete((r, e) -> {
    // whenComplete第一个参数是结果,第二个参数是异常,他可以感知异常,无法返回默认数据
     System.out.println("执行完毕,结果是---" + r + "异常是----" + e);
 }).exceptionally(u -> {
   // exceptionally只有一个参数是异常类型,他可以感知异常,同时返回默认数据10
     return 10;
  // handler既可以感知异常,也可以返回默认数据,是whenComplete和exceptionally的结合
 }).handle((r, e) -> {
     if (r != null) {
         return r;
     }
     if (e != null) {
         return 0;
     }
     return 0;
 });

6.2 线程串行化

/**
 * 3.线程串行化(把后边的线程和前边的串起来)
 */

//thenRunAsync()无返回值
CompletableFuture<Void> voidCompletableFuture1 = CompletableFuture.supplyAsync(() -> {
    System.out.println("线程号为***" + Thread.currentThread().getId());
    int i = 5;
    System.out.println("---------" + i);
    return i;
}, service).thenRunAsync(() -> {
    System.out.println("thenRunAsync,不可接受传来的值,自己无返回值的串行化---");
}, service);

//thenAccept(x)
CompletableFuture<Void> voidCompletableFuture2 = CompletableFuture.supplyAsync(() -> {
    System.out.println("线程号为***" + Thread.currentThread().getId());
    int i = 5;
    System.out.println("---------" + i);
    return i;
}, service).thenAccept((r) -> {
    System.out.println("thenAccept可接受传来的值,自己无返回值的串行化---");
});

//thenApply(x)
CompletableFuture<Integer> voidCompletableFuture3 = CompletableFuture.supplyAsync(() -> {
    System.out.println("线程号为***" + Thread.currentThread().getId());
    int i = 5;
    System.out.println("---------" + i);
    return i;
}, service).thenApply((r) -> {
    System.out.println("thenApply可接受传来的值,自己有返回值的串行化---");
    return  10;
});

6.3 两任务组合

 /**
  * 异步,两任务组合 :两个任务都完成,第三个任务才开始执行
  */

 //定义两个任务
 //任务一
 CompletableFuture<Integer> task1 = CompletableFuture.supplyAsync(() -> {
     int i = 5;
     System.out.println("任务一开始执行" + i);
     return i;
 }, service);

 //任务二
 CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() -> {
     int i = 10;
     System.out.println("任务二开始执行" + i);
     return i;
 }, service);

 //要求:任务一、二都完成后才执行任务三
 // runAfterBothAsync:无传入值、无返回值
 task1.runAfterBothAsync(task2,()->{
     System.out.println("任务三开始执行-runAfterBothAsync:无传入值、无返回值  ");
 },service);

 // thenAcceptBothAsync:有传入值、无返回值
 task1.thenAcceptBothAsync(task2,(x,y)->{
     System.out.println("任务三开始执行-runAfterBothAsync:无传入值、无返回值  task1的结果是x ,task2的结果是y");
 },service);


 // thenCombineAsync:有传入值、有返回值
 task1.thenCombineAsync(task2,(x,y)->{
     System.out.println("任务三开始执行-runAfterBothAsync:无传入值、无返回值  task1的结果是x ,task2的结果是y,task3返回hello");
     return "hello";
 },service);



 /**
  * 异步,两任务组合 :两个任务都完成其中一个完成,第三个任务才开始执行
  */

 /**
  * runAfterEither 无传入值、无返回值
  * acceptEither 有传入值、无返回值
  * applyToEither 有传入值、有返回值
  * 代码同上!
  */

6.4 多任务组合

/**
 * 异步,多任务组合 :多个任务都完成,才进行下一步操作
 * allOf() 等待所有任务完成
 * anyOf()  只要有一个任务完成即可
 * 注意:最后使用get()方法:阻塞式等待所有任务都做完,再进行下一步
 */
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值