小白一文搞懂线程池

为什么使用线程池?

想象一下,你是一家炸鸡店的老板,每次有顾客来点餐,你都要亲自招一个临时工来炸鸡,炸完后又把他解雇。这不仅累,还特别烧钱!如果用线程池,就相当于提前招了一批员工,订单来了直接上手干活,活干完了还能休息,不用反复招聘。

线程池的三大作用

  1. 降低资源消耗:提高线程利用率,降低创建和销毁线程的消耗(减少“面试”的成本)。
  2. 提高响应速度:任务(订单)来了,直接拿线程池的的线程(现成的员工)可执行,而不是先创建线程,再执行。
  3. 提高线程的可管理性:线程是稀缺资源,使用线程池可以统一分配销毁。

PS:也可以类比我们打网球的场景,线程池是装网球的框子,线程是一个个网球,每次训练打网球时会在筐子里准备一筐子网球,每次用一个拿一个,即用即拿,用完之后统一回收
小测试:
如果你的炸鸡店订单量突然暴增,所有员工都在忙碌,而新订单不断涌入,你该怎么办?
A. 再找几个兼职工(救急线程)
B. 让顾客排队等候(任务排队)
C. 告诉顾客:“今天鸡炸完了” (任务拒绝策略)
D. 直接跑路(放弃线程池,自己一个人炸鸡)

线程池有哪些状态?

线程池的状态就像人一天的心情变化,从早上精神满满,到晚上摊在床上。

  • Running(满血开工):这是最正常的状态,可接收新任务,也可处理等待队列中的任务
  • ShutDown(停止接单但继续处理现有炸鸡订单): 不再接收新任务的提交,但会继续处理等待队列中的任务。
  • Stop(怒摔围裙,炸鸡机直接拔掉电源): 不接收新任务的提交,也不处理等待队列中的任务,中断正在执行任务的线程。
  • Tidying(收拾摊子): 所有任务都销毁了,workcount为0,线程池状态在转换为tidying状态时,会执行钩子方法terminated()
  • Terminated(关门大吉): terminated()方法执行结束后,线程池的状态就会变成这个。
    灵魂提问:如果你是炸鸡店老板,你最害怕遇到哪个状态?

线程池有哪些参数?

让我们从老板管理炸鸡店的角度来看这些参数:

  • corePoolSize: 核心线程数,也就是正常情况下能创建的线程数,这些线程创建后并不会消除,而是一种常驻线程(核心员工数,有固定编制的员工)。
  • workQueue: 阻塞队列,用来放等待执行的任务。假设核心线程数被使用完,还有任务进来放队列里,若队列放满则开始创建救急线程(订单缓冲区,订单多了放这里)。
  • maximumPoolSize: 最大线程数目(炸鸡店能雇用的最大人数,包括正式工和临时工)。(注:最大线程数=核心线程数+救急线程数)
  • keepALiveTime:生存时间(针对救急线程),超出生存时间的非核心线程数会被消除(临时工待命时间,超过这个时间,临时工自动走人)。
  • unit:生存时间的时间单位,针对救急线程
  • ThreadFactory: 线程工厂,用来生产线程执行任务。我们可以使用默认的线程工厂:产生的线程都在同一个组内,拥有相同的优先级,且都不是守护线程。我们也可以自定义线程工厂,可以根据业务制定不同的线程工厂(员工招募渠道,决定招来的员工素质,比如只招爱吃炸鸡的)。
  • Handler: 任务拒绝策略(拒单策略)
    问题来了:如果你的炸鸡店(线程池)发现订单爆满,排队都快挤不下了,你会先增加员工(线程)还是扩大排队区(任务队列)?为什么?

任务拒绝策略有哪些?

当订单太多,店里忙不过来时,可以采取的应对策略:
当订单太多,店里忙不过来时,可以采取的应对策略:

  • AbortPolicy(默认策略):直接告诉顾客“今天不炸了”,然后愉快地休息(抛异常)。
  • CallerRunsPolicy(顾客自己下厨):让顾客自己炸鸡,减少压力。
  • DiscardPolicy(直接无视):悄悄把订单扔掉,假装没看到。
  • DiscardOldestPolicy(踢走最早下单的顾客):让最早的顾客走人,把新来的塞进去。

你经营炸鸡店时,会选哪种策略?

线程池的处理流程具体是怎样的?

在这里插入图片描述

  • 线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务。
  • 当线程数达到 corePoolSize 并没有线程空闲时,这时再加入任务,新加的任务会被加入 workqueen队列排队,直到有空闲的线程。
  • 如果队列选择了有界队列,那么任务超过了队列大小时,会创建 maximumPoolSize - corePoolSize 数目的线程来救急。
  • 如果救急线程数也达到上限,这时再来任务会执行 拒绝策略。

没人下单时,炸鸡店(线程池)是空的。
顾客来了(任务提交),马上安排一个员工(线程)开炸。
如果员工全满,订单暂存到队列里(任务队列)。
如果队列也满了,赶紧找外包人员(救急线程)。
实在忙不过来,执行拒单策略。
试想一下,如果你的炸鸡店(线程池)总是满负荷运转,你应该优化哪个环节?

阻塞队列的作用是什么?

一般队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前任务了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务。
阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。
阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活,不至于一直占用cpu资源。

阻塞队列就像顾客排队取号,防止所有人一拥而上,炸鸡店乱成一团:
没有排队机制(普通队列):所有顾客直接冲进厨房,导致混乱。
设定阻塞队列:顾客取号排队,店员一个一个处理订单,保证有序。

为什么新任务来了是先添加队列而不是先创建最大线程?
在创建线程的时候,是要获取全局锁的,这个时候其他的就得阻塞,影响了整体效率。
就好比工厂企业里有10个(core)正式工的名额,最多招10个正式工,要是人数超过正式工人数(take>core)的情况下,工厂领导(线程池)不是先扩招工人,还是这10个人,但是任务可以稍微积压一下,即先放到队列去(代价低)。10个正式工慢慢干,迟早会干完的,要是任务还在继续增加,超过正式工加班忍耐极限了,就找外包(救急线程)帮忙了。要是正式工加外包还是不能完成任务,那新来的任务也会被领导拒绝。

线程池的创建方式

  • newFiexedThreadPool(固定线程池)
    适合炸鸡量稳定的店,正式员工固定,任务再多也不会扩招
    核心线程数=最大线程数,没有救急线程被创建,因此也无需超时时间;阻塞队列是无界的,可以放任意数量的任务。
    总结:适用于任务量已知,相对耗时的任务
@Slf4j
public class ThreadPoolTest {
    public static void main(String[] args) {
        ExecutorService poolabc = Executors.newFixedThreadPool(3);

        poolabc.execute(()->log.info("1"));
        poolabc.execute(()->log.info("2"));
        poolabc.execute(()->log.info("3"));
    }
}
  • newCachedThreadPool(缓存线程池)
    适合订单忽高忽低的店,平时没员工,订单多时疯狂扩招,60s没人下单就裁员。
    核心线程数是0,最大线程数是Integer.MAX_VALUE,救急线程的空闲生存时间是60s,意味着全部都是救急线程(60s后可以回收),救急线程可以无限创建
    队列采用了SynchronousQUeue,实现特点是它没有容量,没有线程来取是放不进去的
    总结:整个线程池表现为线程数根据任务量不断增长,没有上限,当任务执行完毕,空闲一分钟后释放线程。
    适用于任务数比较密集,但每个任务执行时间比较短的情况。

  • newSingleThreadPool(单线程池)
    只有一个员工的炸鸡店,排队机制极强
    只有一个核心线程。任务多于1时,会放入无界队列排队,任务执行完毕,唯一的线程也不会释放。

若自己创建一个单线程执行任务和newSingleThreadPool线程池执行任务的区别?
如果自己创建的任务执行失败而终止,那么没有任何补救措施,而线程池还会创建一个线程,保证池的正常工作。
newScheduledThreadPool
schedule可以延迟执行任务

ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
log.info("start..");
pool.schedule(()->{
    try{
        TimeUnit.SECONDS.sleep(2);
        log.info("1");
    }catch (InterruptedException e){
        e.printStackTrace();
    }
}, 1, TimeUnit.SECONDS);
pool.schedule(()->{
    log.info("2");
}, 1, TimeUnit.SECONDS);

误用线程池带来的问题

  1. 内存溢出:炸鸡店招了太多员工,工资付不起。
  2. 线程过多导致CPU飙高:员工全在干活,导致炸鸡机(CPU)过热。
  3. 任务堆积,响应缓慢:排队区(队列)太大,顾客等到饿晕。

你会如何优化你的炸鸡店(线程池)?
线程池管理就像经营炸鸡店,如何让炸鸡又快又稳,还不至于累垮自己,是一门学问。希望你在学习线程池的同时,也能在现实生活中找到更多类似的管理逻辑,提升你的编程思维!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值