菜鸡每日一面系列打卡23天
每天一道面试题目
助力小伙伴轻松拿offer
坚持就是胜利,我们一起努力!
题目描述
谈谈你对线程池的理解(下)。
题目分析
在上一篇谈谈你对线程池的理解(上)中,菜鸡主要讲述线程池的概念,Java中的线程池以及线程池的生命周期。本文将结合源码介绍线程池的配置以及核心实现。
如果说上一篇是对线程池的整体把握,那么,这篇就是对线程池的细节剖析。接下来,随菜鸡一起去看看吧。
题目解答
在上篇中,我们提到了四种常见的线程工厂。
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:丢弃最早的未被处理的任务。
了解线程池的参数之后,我们来看看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();// 状态为SHUTDOWNadvanceRunState(SHUTDOWN);interruptIdleWorkers();onShutdown();} finally {mainLock.unlock();}tryTerminate();}// 粗暴关闭public List<Runnable> shutdownNow() {List<Runnable> tasks;final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {checkShutdownAccess();// 状态为STOPadvanceRunState(STOP);interruptWorkers();tasks = drainQueue();} finally {mainLock.unlock();}tryTerminate();return tasks;}}
上述主要分享了最常用的execute方法以及上一篇中提到的关闭线程池两种方式的源码。菜鸡在关键位置做了注释,在此就不再赘述。
其实,如果静下心来看,小伙伴们就会发现,JDK的源码条理性是很好的,各种设计的思维细细品味也都能有所领悟,因此,学习优秀的源码对个人成长是非常有帮助的。
如果不知道哪些是优秀的源码,那就从JDK源码看起,从常用的类库的源码看起,在学习原理的同时,学习设计,妙啊……
相关链接
以上便是菜鸡对线程池的总结下篇,供大家参考,结合上篇食用,效果更佳!

学习 | 工作 | 分享

👆长按关注“有理想的菜鸡”




