如果觉得我的文章还不错的话就点个赞,关注一波,转发收藏吧,另外可以微信搜索【佘凡架构师】阅读更多的好文章,获取我为大家准备的资料。
最近有好多同学都在问,面试官为什么那么喜欢问线程池,blabla连环炮一大堆,我们应该怎么应对面试话的连环夺命追击问?
我们知道但凡是一个系统,那么就不会让这个系统无限制的创建很多很多的线程,那么分分钟就OOM,肯定会构建一个线程池,有一定数量的线程,让他们执行各种各样的任务,线程执行完毕任务之后,不会让线程销毁掉自己,继续去执行下一个任务。这样避免线程的创建,初始化,销毁的各种系统资源成本。
简单来讲线程池就是一个池子,里面装有多个线程。有点像我们生活中的游泳池,首先我们会给游泳池的长宽高设置好体积,然后当我们要游泳的时候就往里面注入水。当我们游泳完毕后,水不要了就会做释放。
那线程池也是如此,当我们有任务需要的时候,这样就会生成核心线程处理任务。
那么问题来了,我们的任务太多,核心线程处理不过来怎么办?不用担心,我们有阻塞队列,将任务放入阻塞队列中,等待处理。
那问题又来了,阻塞队列都已经放满了任务,这个时候怎么办?没关系,我们继续生成非核心线程去处理阻塞队列中的任务。
完了完了,连非核心线程都被使用完毕,这个时候应该怎么办?没关系,我们还有终极大招,拒绝策略。
首先我们大池子(线程池),这个线程池里默认只能装3个线程。现在里面是空的,因为没有任务可以执行,所以核心线程还没初始化。
现在进来了3个任务,所以创建了3个核心线程去执行任务
这个时候突然又出现了3个任务,但是线程池中的核心线程已经满了,所以没办法,只能放入阻塞队列。
当任务1执行完毕以后,任务4就会被现在空闲的核心线程a继续执行
这个时候突然又来了3个任务(任务7,任务8,任务9),而阻塞队列中只能放3个任务。并且线程池中的核心线程都还没有执行手头的任务。这个时候我们有非核心线程(假设2个),就会创建非核心线程来处理剩余的任务(处理任务8,任务9)。
假如又有了2个任务(任务10,任务11),这个时候所有的线程还是没有把手头的任务执行完毕。我们就会执行拒绝策略
恰好这个时候突然执行力度增强,一下子线程池中的任务全部执行完毕。那么线程池中的线程会从阻塞队列中取出任务来执行。
之后再也没有任务进入到阻塞队列中,那么非核心线程会开始等待空闲时间。一旦超过空闲时间,那么非核心线程就会自动销毁。最终只有3个核心线程存在
从上面的那几个图我们了解到,线程池有核心线程,非核心线程,阻塞队列,非核心线程的空闲时间,拒绝策略。那让我们来看一下线程池的核心参数代码吧。
public ThreadPoolExecutor(int corePoolSize, //核心线程数
int maximumPoolSize,//最大线程数=核心线程数+非核心线程数
long keepAliveTime,//空闲时间(非核心线程无任务空闲时间)
TimeUnit unit,//空闲单位
BlockingQueue<Runnable> workQueue //阻塞队列
) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory() //默认线程工厂
, defaultHandler //默认拒绝策略(AbortPolicy)
);
}
CallerRunsPolicy : 直接调用当前线程执行任务策略
AbortPolicy :直接抛异常
DiscardPolicy:丢弃当前任务策略
DiscardOldestPolicy:丢掉最后一个阻塞队列中的任务,尝试把当前任务放进去
还有一种自定义策略,实现RejectedExecutionHandler接口。一般我们都是使用这种策略,保证任务的不丢失。
newFixedThreadPool(固定线程池):Executors.newFixedThreadPool(nThreads)
缺点:LinkedBlockingQueue是无界队列,当任务太多,而线程太少,任务处理不过来的时候。太多的任务放在无界队列种容易导致OOM。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newSingleThreadExecutor(单个线程池):Executors.newSingleThreadExecutor()。核心线程与最大线程都是1
缺点:LinkedBlockingQueue是无界队列,当任务太多,而线程太少,任务处理不过来的时候。太多的任务放在无界队列种容易导致OOM。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
newCachedThreadPool(缓存线程池):Executors.newCachedThreadPool()
缺点:最大线程数为Integer.MAX_VALUE,当任务过多,而阻塞队列SynchronousQueue容量为0。创建太多的非核心线程,导致OOM。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
ScheduledThreadPoolExecutor(定时线程池):Executors.ScheduledThreadPoolExecutor(corePoolSize)
缺点:自定义了核心线程数,但是最大线程数依旧为Integer.MAX_VALUE。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
一般正常情况就是生产环境突然宕机,线程池中的积压的请求任务当即立即全部丢失,导致线上数据不一致情况。那我们应该怎么解决上述的这个问题?解决方案就是将任务提交到线程池去执行任务时,先在库表里新增一条这样的数据,并且把状态改为未完成。等任务执行完毕以后同步更新库表信息为已完成。使用这样的方式来保证就算生产环境宕机,我们也能够从库表的数据进行回溯。
生产环境使用无界队列时,若请求任务数不断的增大,会使得当前的阻塞无界队列中不断堆加新的请求任务,直接让内存飙升。更有甚者,直接OOM。
同上,生产环境中的阻塞队列满了以后,会不断的创建新的非核心线程来处理任务。它的好处就是当请求任务一下飙升堆积过来之后,能够创建大量的线程快速处理,对并发的请求任务进行削峰处理。缺点就是一旦创建了大量的线程,会使得CPU和内存同时飙升。大量的线程在执行任务时会占用CPU的资源,大量的线程创建会占用内存资源。