线程池的核心原理篇

发布于:2022-11-09 ⋅ 阅读:(340) ⋅ 点赞:(0)

前言:大家好,我是小威,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;
    }

由上面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);
    }

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

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

在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());
        }
    }

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

    public static class DiscardPolicy implements RejectedExecutionHandler {
        public DiscardPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

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);
            }
        }

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

    public static class CallerRunsPolicy implements RejectedExecutionHandler {
        public CallerRunsPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

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

线程池的关闭方法

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

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

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

在这里插入图片描述

本文含有隐藏内容,请 开通VIP 后查看