JUC-多线程(8.线程池)学习笔记

1. 简介

1. 特点

  • 线程池做的工作主要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量 超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
  • 它的主要特点为:线程复用 ; 控制最大并发数 ; 管理线程

2. 优点

  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
  2. 提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。
  3. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

2. 使用

1. 架构

  • Java中的线程池是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类,主要使用 ExecutorService 接口
    在这里插入图片描述

2. 代码实例

  1. Executors.newFixedThreadPool(int)
  • 是用于执行长期任务性能好,有固定的线程数,创建一个线程池,一池有N个固定的线程
  1. Executors.newSingleThreadExecutor()
  • 一个任务一个任务的执行,一池一线程
  1. Executors.newCachedThreadPool()
  • 执行很多短期异步任务,线程池根据需要创建新线程,但在先前构建的线程可用时将重用它们。可扩容,遇强则强
public class JUC10_ThreadPool {
    public static void main(String[] args) {
        // 1. 一 个线程池受理 五 个线程
//        ExecutorService threadpool = Executors.newFixedThreadPool(5);
        // 2. 一 个线程池受理 一 个线程
//        ExecutorService threadpool = Executors.newSingleThreadExecutor();
        // 3. 一 个线程池受理 N 个线程,可扩容
        ExecutorService threadpool = Executors.newCachedThreadPool();

        try {
            //模拟有 10 个顾客来办理业务,但只有 5 个办理窗口
            for (int i = 1; i <= 10; i++) {
                threadpool.execute(()->{
                    System.out.println("当前"+ Thread.currentThread().getName()+"办理业务");
                });


                //模拟办理所需时间
//                try {
//                    TimeUnit.MILLISECONDS.sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            threadpool.shutdown();
        }
    }
}

3. 底层源码

在这里插入图片描述

  • 由图可知上述三个 API 底层调用的都是同一个方法 —— threadPoolExecutor
  • 第五个参数表示一个阻塞队列
    • LinkedBlockingQueue:由链表结构组成的有界(但大小默认值为integer.MAX_VALUE)阻塞队列。
    • SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列。(生产一个,消费一个)

4. threadPoolExecutor 7 个重要参数

在这里插入图片描述

1. int corePoolSize

  • 线程池中的常驻核心线程数,最少存在的线程数

2. int maximumPoolSize

  • 线程池中能够容纳同时执行的最大线程数,此值必须大于等于1

3. long keepAliveTime

  • 多余的空闲线程的存活时间
  • 当前池中线程数量超过corePoolSize时,且当空闲时间达到keepAliveTime时,多余线程会被销毁直到只剩下corePoolSize个线程为止

4. TimeUnit unit

  • keepAliveTime的单位 —— 秒 / 毫秒 / 微秒

5. BlockingQueue workQueue

  • 任务队列,被提交但尚未被执行的任务

6. threadFactory threadFactory

  • 表示生成线程池中工作线程的线程工厂,用于创建线程,一般默认的即可

7. RejectedExecutionHandler handler

  • 拒绝策略,表示当队列满了,并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何来拒绝
    请求执行的runnable的策略

5. 底层原理

  1. 在创建了线程池后,开始等待请求。
  2. 当调用 execute() 方法添加一个请求任务时,线程池会做出如下判断:
    1. 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
    2. 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入 workQueue队列
    3. 如果这个时候 workQueue队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行 workQueue队列 中的任务;
    4. 如果workQueue队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行
  3. 当一个线程完成任务时,它会从workQueue队列中取下一个任务来执行。
  4. 当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
    1. 如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。
    2. 所以线程池的所它最终会收缩到 corePoolSize 的大小**。

在这里插入图片描述

6. 线程池的选择

  • 在工作中单一的/固定数的/可变的三种创建线程池的方法哪个用的多?及为什么?
  • 答 : 一个都不用
    在这里插入图片描述

7. 自定义线程池

1. 代码示例

public static void main(String[] args) {
        ExecutorService threadpool = new ThreadPoolExecutor(2,5, 2L,TimeUnit.SECONDS,
                                                            new LinkedBlockingQueue<>(3),
                                                            Executors.defaultThreadFactory(),
                                                            new ThreadPoolExecutor.AbortPolicy());

        try {
        	// 改变任务数,观察输出结果
            //模拟有 N 个顾客来办理业务
            for (int i = 1; i <= 5; i++) {
                threadpool.execute(()->{
                    System.out.println("当前\t"+ Thread.currentThread().getName()+"\t办理业务");
                });
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            threadpool.shutdown();
        }
    }
  • 任务队列(workQueue)的参数如果不写就默认为 Integer.MAX_VALUE,所以需要写上
    在这里插入图片描述

  • 线程工厂使用默认的,模仿源码
    在这里插入图片描述

  • 拒绝策略也暂时使用和默认的,下文具体阐述

2. 改变任务的数量

  1. 5 个任务
  • 不会报错,正常执行,两个线程处理完成
  1. 8 个任务
  • 不会报错,正常执行,五个线程处理完成
  1. 9 个任务
  • 会报错,会报 RejectedExecutionException
  1. 结论
  • 线程池的最多可以容纳数 =【最大线程数(maximumPoolSize) + 任务队列(workQueue)可容纳数】

3. 最大线程数的设置规则

  1. 如果是 CPU 密集型(CPU 用的最多):maximumPoolSize(最大线程数) = CPU核数 + 1
  • 实际情况种不能写死
// 获取电脑的 CPU 核数
int CPU = Runtime.getRuntime().availableProcessors();
int maximumPoolSize = CPU + 1;
  1. 如果是 IO 密集型 : maximumPoolSize(最大线程数)= CPU核数 / 阻塞系数

8. 拒绝策略

1. 简介

  • 等待队列已经排满了,再也塞不下新任务了,同时,线程池中的max线程也达到了,无法继续为新任务服务
  • 也即 最多可以容纳数 达到最大
  • 这个是时候我们就需要拒绝策略机制合理的处理这个问题。

2. 分类(4 种)

  1. ThreadPoolExecutor.AbortPolicy()
  • 解释 : 当线程池中任务数量超出 最多可容纳数 时,会直接抛出 RejectedExecutionException异常 阻止系统正常运行
  • Executor 默认的策略
    在这里插入图片描述
    在这里插入图片描述
  1. ThreadPoolExecutor.CallerRunsPolicy
  • “调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
  • 举例 : 营业厅A 人多,告诉顾客去 营业厅B 办理 ,可是顾客来到 营业厅B 时, 营业厅B 人更多,便告诉顾客再去 营业厅A 看看。 —— 即不放弃任何一个顾客 ; 营业厅B 把顾客 回退营业厅A
  1. ThreadPoolExecutor.DiscardOldestPolicy
  • 抛弃队列中等待最久的任务,然后把当前任务加人队列中尝试再次提交当前任务。
  1. ThreadPoolExecutor.DiscardPolicy
  • 该策略默默地丢弃无法处理的任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种策略。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

yuan_404

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

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

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

打赏作者

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

抵扣说明:

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

余额充值