• Java多线程(7)----浅谈线程池


    1,前言

    Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或者并发执行任务的程序都可以使用线程池。线程池能带来的好处:

    • 降低资源消耗:重复利用已创建的线程降低线程创建和销毁造成的消耗
    • 提高响应速度:当任务到达后,可以使用已经创建好的线程来执行任务
    • 提高线程的可管理性:无限制的创建线程会降低系统的稳定性,使用线程池可以统一分配、调优和监控

    2,线程池

    当提交一个新的任务到线程池后,线程池的处理流程如下:

    1. 判断核心线程池中的线程数是否小于corePoolSize指定的数量
    2. 如果是,就继续创建核心线程完成任务(执行这一步骤需要获取全局锁,因为获取全局锁是一个严重的可伸缩瓶颈,应避免)
    3. 如果不是,会将任务加入任务队列中
    4. 当任务队列满了之后,如果还有新的任务进来,就判断创建新的线程是否会使线程总数大于maximumPoolSize
    5. 如果不是,创建新的线程并执行任务
    6. 如果是,执行拒绝策略

    值得注意的是,当线程池中的线程数大于等于corePoolSize,线程池也就完成了预热,之后再进来新任务,几乎所有的任务都是执行步骤3【大致流程可以理解为:优先让核心线程池的线程数达到充盈,借助新起的其他工作线程来辅助完成额外的任务】

    流程示意图如下:
    在这里插入图片描述在这里插入图片描述

    工作线程:线程池创建线程时,会将线程封装成工作线程 WorkerWorker 在执行完 任务后,还会循环获取工作队列里的任务来执行。

    线程池中的线程执行任务分两种情况,如下:

    1. execute()方法中创建一个线程时,会让这个线程执行当前任务
    2. 这个线程执行完当前的任务后,会反复从BlockingQueue获取任务来执行

    2.1,创建

    使用ThreadPoolExecutor来创建一个线程池

        public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory,
                                  RejectedExecutionHandler handler
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这几个相关的参数如下:

    • corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建 一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程, 等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的 prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程
    • maximumPoolSize:线程池允许创建的最大线程数【如果任务队列使用的是无界队列,该参数无效】
    • keepAliveTime:线程保持活动时间。线程池的工作线程空闲后,保持存活的时间
    • unit:线程保持活动时间的时间单位
    • workQueue:用于保存等待执行的任务的阻塞队列(队列满时,阻塞插入操作;队列空时,阻塞获取操作)。可以选择以下几个阻塞队列:
      • ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO原则对元素进行排序
      • LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按 FIFO 排序元 素,吞吐量通常要高于 ArrayBlockingQueue
      • SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个 线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于 Linked-BlockingQueue
      • PriorityBlockingQueue:一个具有优先级的无限阻塞队列
    • threadFactory:设置创建线程的工厂
    • handler:饱和策略,当队列和线程池都满了,说明线程池处 于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是 AbortPolicy,表示无法处理新任务时抛出异常。在 JDK 1.5 中 Java 线程池框架提 供了以下 4 种策略
      • AbortPolicy:直接抛出异常
      • CallerRunsPolicy:只用调用者所在线程来执行任务
      • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务
      • DiscardPolicy:不处理,丢弃掉
      • 可以通过实现RejectedExecutionHandler接口自定义策略

    2.2,向线程池提交任务

    可以使用两个方法向线程池提交任务:

    • execute()
    • submit()

    execute()

    execute()用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。它需要传入一个Runnable类的实例

    public void execute(Runnable command) {
        ......
    }
    
    • 1
    • 2
    • 3

    submit()

    submit()用于提交需要返回值的任务,线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否成功,并且通过get()获取返回值,get()会阻塞当前线程直到任务完成。

        Future<Object> future = executor.submit(harReturnValuetask);
        try {
            Object s = future.get();
        } catch (InterruptedException e) {
            // 处理中断异常
        } catch (ExecutionException e) {
            // 处理无法执行任务异常
        } finally {
            // 关闭线程池
            executor.shutdown();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2.3,关闭线程池

    可以通过调用线程池的shutdownshutdownNow来关闭线程池。它们的原理时遍历线程池中的工作线程,逐个调用线程的interrupt()来中断线程,所以无法响应中断的任务可能永远无法终止。

    区别

    • shutdownNow:首先湘江线程池的状态设置为STOP,然后尝试停止所有正在执行或者暂停任务的线程,并返回等待执行任务的列表
    • shutdown:将线程池的状态设置为SHUTWODN状态,然后中断所有没有正在执行的线程

    只要调用了这两个关闭方法中的任意一个,isShutdown 方法就会返回 true。当所有 的任务都已关闭后,才表示线程池关闭成功,这时调用 isTerminaed 方法会返回 true。至 于应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown方法来关闭线程池,如果任务不一定要执行完,则可以调用 shutdownNow 方 法

  • 相关阅读:
    什么是DNS域名解析
    黄金代理前景如何,有得搞吗?
    记 350亿数据从 es 迁移到 ClickHouse 遇到的问题
    这个Python读取文件的方法,堪称天花板级别...
    C# Onnx LSTR 基于Transformer的端到端实时车道线检测
    2022 年杭电多校第八场补题记录
    面试官:什么是伪共享,如何避免?
    你可能忘了的HTML知识 建议看看
    Python对象序列化和反序列化pickle和marshal
    Android shape记录
  • 原文地址:https://blog.csdn.net/weixin_41043607/article/details/125470401