每日一面——谈谈你对线程池的理解(下)

菜鸡每日一面系列打卡23

每天一道面试题目 

助力小伙伴轻松拿offer

坚持就是胜利,我们一起努力!

题目描述

谈谈你对线程池的理解(下)。

题目分析

在上一篇谈谈你对线程池的理解(上)中,菜鸡主要讲述线程池的概念,Java中的线程池以及线程池的生命周期。本文将结合源码介绍线程池的配置以及核心实现。

如果说上一篇是对线程池的整体把握,那么,这篇就是对线程池的细节剖析。接下来,随菜鸡一起去看看吧。

题目解答

01

线程池的配置

在上篇中,我们提到了四种常见的线程工厂。

  • newFixedThreadPool:创建固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量后不再变化。

  • newSingleThreadExecutor:单线程的Executor,创建单个工作线程执行任务,可以确保按照任务在队列中的顺序来串行执行。

  • newCachedThreadPool:创建可缓存的线程池,如果线程池的当前规模超过处理需求时,回收空闲线程,反之,创建新的线程。

  • newScheduledThreadPool:创建固定长度的线程池,并且能以延迟或定时的方式执行任务。

它们的实现都或直接或间接地使用到了一个非常重要的类ThreadPoolExecutor。这个类的构造方法设置了线程池的必要参数。我们从源码入手,看一下ThreadPoolExecutor类的构造参数有哪些必要参数。

public class ThreadPoolExecutor extends AbstractExecutorService {


    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        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;
    }


}

由源代码可知,该构造方法有七个参数,我们来看一下每个参数的具体含义。

  • corePoolSize:线程池的基本大小,也即线程池的目标大小,即在没有任务执行时线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。

  • maximumPoolSize:线程池的最大大小,表示可同时活动的线程数量的上限。

  • keepAliveTime:如果某个线程的空闲时间超过了存活时间,那么将被标记为可回收的,并且当线程池的当前大小超过了基本大小时,这个线程将被终止。

  • unit:keepAliveTime的单位。

  • workQueue:用于保存等待执行任务的队列。ThreadPoolExecutor允许提供一个BlockingQueue来保存等待执行的任务。基本的任务排列方法有3种:

    • 无界队列:在任务急剧增加时容易导致资源耗尽;

    • 有界队列:在队列满之后,需要相应的饱和策略来应对;

    • 同步移交:在线程池达到最大线程数且无空闲队列时,需要相应的饱和策略来应对。

  • threadFactory:线程工厂,在ThreadFactory中只定义了一个方法newThread,每当线程池需要创建一个新线程时都会调用该方法。

  • handler:饱和策略。主要有4种:

    • AbortPolicy:终止,抛出RejectedExecutionException,默认饱和策略;

    • CallerRunsPolicy:将某些任务回退到调用者,降低新任务的流量;

    • DiscardPolicy:直接丢弃;

    • DiscardOldestPolicy:丢弃最早的未被处理的任务。

02

源码分析

了解线程池的参数之后,我们来看看ThreadPoolExecutor类的核心部分源码。

public class ThreadPoolExecutor extends AbstractExecutorService {
    // 执行
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        // 当前任务数小于线程池的基本大小
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // 线程池状态isRunning并且工作队列可以加入
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // 通过addWorker(command, false)新建线程执行任务,若失败则拒绝。
        else if (!addWorker(command, false))
            reject(command);
    }
    
    // 平缓关闭
    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            // 状态为SHUTDOWN
            advanceRunState(SHUTDOWN);
            interruptIdleWorkers();
            onShutdown();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }
    
    // 粗暴关闭
    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            // 状态为STOP
            advanceRunState(STOP);
            interruptWorkers();
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }
}

上述主要分享了最常用的execute方法以及上一篇中提到的关闭线程池两种方式的源码。菜鸡在关键位置做了注释,在此就不再赘述。

其实,如果静下心来看,小伙伴们就会发现,JDK的源码条理性是很好的,各种设计的思维细细品味也都能有所领悟,因此,学习优秀的源码对个人成长是非常有帮助的。

如果不知道哪些是优秀的源码,那就从JDK源码看起,从常用的类库的源码看起,在学习原理的同时,学习设计,妙啊……

相关链接

每日一面——谈谈你对线程池的理解(上)

以上便是菜鸡对线程池的总结下篇,供大家参考,结合上篇食用,效果更佳!

学习 | 工作 | 分享

????长按关注“有理想的菜鸡

只有你想不到,没有你学不到

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值