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

点击关注公众号,利用碎片时间学习


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()方法:阻塞式等待所有任务都做完,再进行下一步
 */

来源:blog.csdn.net/qq_45076180/article/details/107221610

推荐:

最全的java面试题库

线程池的创建方式,为什么阿里推荐自定义线程池?
PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。“在看”支持我们吧!

原文始发于微信公众号(Java笔记虾):线程池的创建方式,为什么阿里推荐自定义线程池?

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/41568.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!