• 线程池的核心原理篇


    前言:大家好,我是小威,24届毕业生。本篇将记录线程池的核心原理,方便加深知识印象和复习使用。
    本篇文章记录的基础知识,适合在学Java的小白,也适合复习中,面试中的大佬🤩🤩。
    如果文章有什么需要改进的地方还请大佬不吝赐教👏👏。
    小威在此先感谢各位大佬啦~~🤞🤞
    在这里插入图片描述

    🏠个人主页:小威要向诸佬学习呀
    🧑个人简介:大家好,我是小威,一个想要与大家共同进步的男人😉😉
    目前状况🎉:24届毕业生,在一家满意的公司实习👏👏

    🎁如果大佬在准备面试,可以使用我找实习前用的刷题神器哦刷题神器点这里哟
    💕欢迎大家:这里是CSDN,我总结知识的地方,欢迎来到我的博客,我亲爱的大佬😘

    在这里插入图片描述

    以下正文开始

    线程池的作用

    线程池有很多积极的作用,线程池能够提高系统资源的利用率,比如可以重复利用执行完当前任务的线程来执行其他任务,提高线程的复用率;同时线程池能够提高任务的响应速度。不需要每次执行任务时都重新创建线程,当任务到达时不需要等待创建线程就能执行任务。

    线程池的状态

    线程池在运行的过程中会涉及到几种状态的转换:RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED等,它们位于线程池核心类ThreadPoolExecutor下,接下来对几种状态进行分析:

    RUNNING:RUNNING状态表示线程池位于运行状态。在此状态下,线程池能够接受新提交的任务,也能够处理阻塞队列中的任务。

    SHUTDOWN:SHUTDOWN状态表示线程池处于关闭状态。在此状态下,线程池不能接收新提交的任务,但是不会中断当前执行任务的线程,也能够处理阻塞队列中已经保存的任务。在线程池处于RUNNING状态时,当调用线程池的shutdown()方法时会使线程池处于SHUTDOWN状态。

    STOP:SHUTDOWN状态表示线程池处于停止状态。在此状态下,线程池会中断当前正在执行任务的线程,使正在执行的任务被中断;同时线程池不能接收新的任务,也不能处理阻塞队列中保存的任务。在线程池处于RUNNING状态或SHUTDOWN状态时,当调用线程池的shutdownNow()方法时会使线程池处于STOP状态。

    TIDYING:TIDYING状态表示线程池中的任务全部执行完毕,活动线程(有效线程)数为0,即线程池中的工作线程为空,阻塞队列为空,线程池中的工作线程数量为0,此时线程池会处于TIDYING状态。

    TERMINATED:TERMINATED状态表示终结状态,当线程处于TIDYING状态时,调用线程池的terminated()方法会使线程池进入TERMINATED状态。
    在这里插入图片描述

    线程池的转换过程

    线程池中的状态并不是一成不变的,上面介绍了线程池的几种状态,接下来介绍线程池从RUNNING状态转换为TERMINATED状态的流程:

    当线程池处于RUNNING状态时,调用线程池中的shutdown()方法会使线程池从RUNNING状态转变为STOP状态;调用线程池中的shutdownNow()方法时会使线程池处于STOP状态;

    当线程池处于SHUTDOWN状态时,调用线程池中的shutdownNow()方法时会使线程池处于STOP状态;如果线程池中工作线程数为0,阻塞队列为空,那么会从SHUTDOWN状态转变为TIDYING状态;

    当线程处于TIDYING状态时,调用线程池的terminated()方法会使线程池转换为TERMINATED状态。

    在这里插入图片描述

    通过ThreadPoolExecutor类创建线程池

    可以通过工具类Executors创建线程池,其实Executors工具类创建线程池大部分调用的是ThreadPoolExecutor类中的构造方法,由于代码长度,ThreadPoolExecutor类中的构造方法截图如下:
    在这里插入图片描述
    ThreadPoolExecutor类创建线程池的核心代码如下:

        public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory,
                                  RejectedExecutionHandler handler) {
            //核心线程数<0,最大线程数<=0,最大线程数<核心线程数,保持存活时间<0都是不合法的参数,抛出异常
            if (corePoolSize < 0 ||
                maximumPoolSize <= 0 ||
                maximumPoolSize < corePoolSize ||
                keepAliveTime < 0)
                throw new IllegalArgumentException();
            //工作队列为null,线程工厂为null,拒绝策略为null都会抛出空指针异常
            if (workQueue == null || threadFactory == null || handler == null)
                throw new NullPointerException();
            this.acc = System.getSecurityManager() == null ?
                    null :
                    AccessController.getContext();
            this.corePoolSize = corePoolSize;
            this.maximumPoolSize = maximumPoolSize;
            this.workQueue = workQueue;
            this.keepAliveTime = unit.toNanos(keepAliveTime);
            this.threadFactory = threadFactory;
            this.handler = handler;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    由上面ThreadPoolExecutor类构造方法的代码可以看出,需要传递七个参数,七个参数的含义如下:

    corePoolSize:表示线程池中核心线程的数量;

    maximumPoolSize:表示线程池中最大线程数量;

    keepAliveTime:针对救急线程的存活时间的变量,就是当线程数量超过线程池中的核心数量时,如果没有新的任务被提交,那么核心线程外的救急线程就会保持在keepAliveTime变量值之后被销毁,而不是立即销毁;

    unit:也是针对救急线程,表示keepAliveTime的核心单位;

    workQueue:表示线程池中的阻塞队列,阻塞队列中存储着等待执行的任务;

    threadFactory:表示用来创建线程的线程工厂。创建线程池的时候,默认的线程工厂创建的线程具有相同的优先级,可以为线程起一个好名字;

    handler:表示线程池的拒绝策略。当线程池中的阻塞队列满了的时候,线程数maximumPoolSize也达到了最大值,如果此时再有任务提交到线程池,线程池就会采取策略来拒绝任务的执行。
    在这里插入图片描述

    线程池执行任务的流程

    上面介绍了线程池的状态和ThreadPoolExecutor类构造方法的变量,接下来分析向线程池提交任务时,线程池的执行流程:
    提交任务时,首先会判断线程池中的线程数是否达到了核心线程数,如果线程池中运行的线程数小于corePoolSize,当向线程池中提交事务时,即使线程池中存在空闲线程,那么也会创建新的线程来执行任务;

    当线程池中运行的线程数大于核心线程数corePoolSize同时小于最大线程数maximumPoolSize时,那么会判断工作队列workQueue是否已满,如果没有满,那么将任务添加到队列中等待空闲线程执行;

    如果等待队列满了,那么会判断线程池中的线程是否达到了最大线程数maximumPoolSize,如果没有达到,则会创建新的线程执行任务;

    如果达到了最大线程数maximumPoolSize,那么线程池会执行拒绝策略来拒绝任务的执行。
    在这里插入图片描述

    线程池的拒绝策略

    上面介绍到线程池中的线程数达到了最大线程数,线程池中的workQueue阻塞队列也已经满了,当再有任务提交到线程池中时,线程池会采取拒绝策略来拒绝任务的执行。
    线程池的拒绝策略方法在ThreadPoolExecutor类中,其源码如下:

        final void reject(Runnable command) {
            handler.rejectedExecution(command, this);
        }
    
    • 1
    • 2
    • 3

    在ThreadPoolExecutor类的reject方法中,又调用了handler的rejectedExecution方法,点入此方法得到以下源码:

    public interface RejectedExecutionHandler {
        void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
    }
    
    • 1
    • 2
    • 3

    在RejectedExecutionHandler接口中,定义了rejectedExecution(Runnable r, ThreadPoolExecutor executor)方法。继续向下看,有四个类实现了RejectedExecutionHandler接口,分别为AbortPolicy,CallerRunsPolicy,DiscardOldestPolicy,DiscardPolicy这四个类。
    在这里插入图片描述

    AbortPolicy策略:此策略为默认的拒绝策略。在拒绝任务时,会直接抛出异常RejectedExecutionException (属于RuntimeException),让你感知到任务被拒绝了,可以根据业务逻辑选择重试或者放弃提交等方法。

        public static class AbortPolicy implements RejectedExecutionHandler {
            /**
             * Creates an {@code AbortPolicy}.
             */
            public AbortPolicy() { }
    
            /**
             * Always throws RejectedExecutionException.
             *
             * @param r the runnable task requested to be executed
             * @param e the executor attempting to execute this task
             * @throws RejectedExecutionException always
             */
            public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
                throw new RejectedExecutionException("Task " + r.toString() +
                                                     " rejected from " +
                                                     e.toString());
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    DiscardPolicy策略:顾名思义,当新任务被提交后直接被丢弃掉,不会给出任何通知,相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。

        public static class DiscardPolicy implements RejectedExecutionHandler {
            public DiscardPolicy() { }
            public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    DiscardOldestPolicy策略:顾名思义,该策略会丢掉老的节点。如果线程池没有被关闭,同时没有能力执行任务,则会丢弃任务队列中的头结点,通常是存活时间最长的任务,这种策略与第二种不同之处在于它丢弃的不是最新提交的,而是队列中存活时间最长的,这样就可以腾出空间给新提交的任务,但同理它也存在一定的数据丢失风险。

       public static class DiscardOldestPolicy implements RejectedExecutionHandler {
            public DiscardOldestPolicy() { 
            }
            public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
                if (!e.isShutdown()) {
                    e.getQueue().poll();
                    e.execute(r);
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    CallerRunsPolicy策略,相对而言此策略就比较完善了,当有新任务提交后,如果线程池没有被关闭,也没有能力执行,则把这个任务交于提交任务的线程执行,也就是谁提交任务,谁就负责执行任务。这种方式会使得新提交的任务不会被抛弃,不会造成损失。

        public static class CallerRunsPolicy implements RejectedExecutionHandler {
            public CallerRunsPolicy() { }
            public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
                if (!e.isShutdown()) {
                    r.run();
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    除了上述的几种拒绝策略,我们也可以自定义我们自己的拒绝策略,如果想使用我们自定义的拒绝策略,只需要与上述几个策略一样,实现RejectedExecutionHandler接口,重写里面rejectedExecution(Runnable r, ThreadPoolExecutor e)的方法即可。
    在这里插入图片描述

    线程池的关闭方法

    上文提到过,关闭线程池有两种方式,第一种是调用shutdown方法,第二种是调用shutdownNow方法关闭线程池。
    在调用shutdown方法关闭线程池时,线程池不能接收新提交的任务,但是不会中断正在执行任务的线程,同时能够处理阻塞队列中已经保存的任务。待线程池中的任务全部执行完毕。线程池才会关闭。
    在调用shutdownNow)方法关闭线程池时,线程池不能接收新提交的任务,也不能继续处理阻塞队列中的任务,同时,还会中断正在执行任务的线程,使得正在执行的任务被中断,线程池立即关闭并抛出异常。

    本篇文章就分享到这里了,后续将会分享各种其他关于并发编程的知识,感谢大佬认真读完支持咯 ~
    在这里插入图片描述

    文章到这里就结束了,如果有什么疑问的地方请指出,诸佬们一起讨论🍻
    希望能和诸佬们一起努力,今后进入到心仪的公司
    再次感谢各位小伙伴儿们的支持🤞

    在这里插入图片描述

  • 相关阅读:
    (路径、文件)常用命令
    39.SSH远程终端连接工具
    Springboot+基于微信小程序的商城 毕业设计-附源码191145
    【UCIe】UCIe Runtime Link Test Using Parity
    机器学习-波士顿房价预测
    国货之光!ATECLOUD—功能如此强大的测试测量上位机开发工具软件!
    入行测试6年了,从月薪3000到30000,浅谈我的自动化测试进阶之路...
    奇瑞新能源旗下新车——无界Pro上市,雷霆机甲炫酷登场
    T5 model
    动态内存开辟(上)
  • 原文地址:https://blog.csdn.net/qq_53847859/article/details/127720296