线程池的核心原理篇

举报
酸菜鱼. 发表于 2022/11/30 21:15:38 2022/11/30
【摘要】 线程池的作用线程池有很多积极的作用,线程池能够提高系统资源的利用率,比如可以重复利用执行完当前任务的线程来执行其他任务,提高线程的复用率;同时线程池能够提高任务的响应速度。不需要每次执行任务时都重新创建线程,当任务到达时不需要等待创建线程就能执行任务。 线程池的状态线程池在运行的过程中会涉及到几种状态的转换:RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED等...

线程池的作用

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

线程池的状态

线程池在运行的过程中会涉及到几种状态的转换: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)方法关闭线程池时,线程池不能接收新提交的任务,也不能继续处理阻塞队列中的任务,同时,还会中断正在执行任务的线程,使得正在执行的任务被中断,线程池立即关闭并抛出异常。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。