怎么给女朋友讲明白线程池?


前言

线程池是Java面试必问问题之一!

有没有对源码滚瓜烂熟的童鞋?请举手!  ‍♂️(怎么没人举手。。)


对了,今天先来撒一波狗狼~

dee920096620e63b3acc6f16d66cf8eb.png

(表打我~)

来,介绍下:


7000fecaeb27a39084c6ba12ce556946.jpeg


她叫码妞,是我码仔的女朋友喔!

她也在学习各类前端技术,可厉害了!

大家鼓掌欢迎吧!以后她会经常来问我问题的,要被烦了~

最近码妞也在看Java线程池呢,已经看得一头雾水了,正准备去问问码仔,

看码仔能不能给她讲明白了!



734340d9cd7b86dc86710bed293d4d67.jpegbde9a8e7a3e7506e99e7f631f575dea1.jpeg






134d0deaf3537862a053a62bb4bf0c8f.jpeg3d3645a2ce2d92636e9e0d23839ac294.jpeg









c9a7830d063861c88c5081048a183445.jpeg6333ee1906410d6d96e9a8851adcd165.jpeg



线程


线程是一种资源,并不是只存在程序的世界里。

程序,本来就是对生活的一种抽象表述。

比如像车站的售票窗口、退票窗口、检票窗口,每个窗口都在做不同的事情,就是车站里同时运行着的不同线程。

线程多了,需要管理,不同的线程也要能保证不会互相干扰,各做各的。



a7d945a78185f1f0055e4e6ead3444f7.jpeg




bf72281851da4c63a42f3b660083e48b.jpeg



线程的生命周期

这个图很熟悉的吧~


7c520e4114b9c2b00d3b838e76640553.jpeg



98782f13b21dd60e5f690f3931d81e2e.jpeg



好,开始讲线程池啦~


ThreadPoolExecutor


线程池源码里最主要的类了~


看下开头的这段注释:

1eea1fea21a46a6fdeae195d2e011874.jpeg


看到英文就头晕?没事啦~


主要讲线程池重要的两个状态:

  • runState:线程池运行状态
  • workerCount:工作线程的数量

f7db8dd56b587cbb7b1b4153f082bb75.jpeg

线程池用一个32位的int来同时保存runState和workerCount,其中高3位(第31到29位)是runState,其余29位是workerCount(大约500 million)。

来看看存储结构(码仔手动画的哦)

dc371a1defaa28e53f26aa22e6d4580c.jpeg

它的构造方法

c571233653242beed52b14e48bd8f97c.jpeg


629009101b65ce029d23bbdceb4e07c7.jpeg



01ae536ca3253f1f291427f53959d6bf.jpeg



  • corePoolSize

核心线程数,好比班干部的人数。


  • maximumPoolSize

最大线程数,好比教室里的座位数。 当提交任务数超过了这个最大值,线程还有拒绝策略——RejectExecutionHandler,做不动了嘛。


  • keepAliveTime

除核心线程外的空闲线程保持存活时间。 当线程池里线程数超过corePoolSize数量了,keepAliveTime时间到,就把空闲线程关了,不然也闲置了呀,节省能量嘛。


  • workQueue
    任务阻塞队列。通过workQueue,线程池实现了阻塞功能。 当线程池中的线程数超过它的corePoolSize的时候,线程会进入阻塞队列进行阻塞等待。
  • threadFactory
    创建线程的工厂。所有的线程都是通过这个Factory创建的。
    默认会使用
    Executors.defaultThreadFactory() 来作线程工厂。

  • handler 线程池的饱和策略。做不了任务了找理由罢工

dee920096620e63b3acc6f16d66cf8eb.png


  • AbortPolicy
    • 直接抛出异常,默认策略;
  • CallerRunsPolicy
    • 用调用者所在的线程来执行任务;
  • DiscardOldestPolicy
    • 丢弃阻塞队列中靠最前的任务,并执行当前任务;
  • DiscardPolicy
    • 直接丢弃任务。



9ad2cec7d011e29e89fc289e61c3004a.jpeg



d4317ff867a4911cd309447eae11146e.jpeg



Worker来了!



516c1cfa95d34167225f16cfea3d78b2.jpeg


你看Worker的定义,其实它就是封装了的工作线程~

2728adcf4a4fa0e463b0410d439e3e52.png

Worker既实现了Runnable,又继承了AbstractQueuedSynchronizer(AQS),所以它既是一个可执行的任务,又可以达到锁的效果。



5cab1127aa73410ab6e831cf5a9e59bb.jpeg1244d62a721708d0246982db63cf612d.jpeg






看看Worker构造方法:

44a6a585218e252dd192f1bb1e1f23bb.jpeg


946250b825aa871b4afbd2ae1a1dd4bc.jpegd99d6a6754388fa741a7077cd6f3668a.jpeg







f97f64e0fe731cebe6f5fbb2faf0cd7b.jpegc8ac705e8f722c0505a9a5eb7c5838a9.jpeg




线程池是怎么工作的?


DuangDuangDuang!



4e8218e9623e3c86db6f70cc48ed10e6.jpeg



cdff4091c18328e54c190abcd3f7c27e.jpeg88a3e042f2b3e7ded9dfdb79ba9b6a0a.jpeg10acfe8eda8aca91de6ba6bd09f005fb.jpegd9241d29dbe7dec01396672d4c469bda.jpeg

看execute方法里的注释,一步步说得很清楚。


  1. 如果当前正在运行的线程数 < corePoolSize,尝试用给到的command来启动一个新线程作为第一个任务。
    调用addWorker方法,检查runState和workerCount,并且如果增加线程的话,能防止产生错误警报,如果不能增加线程,则返回false。
  2. 如果一个任务被成功地加到队列里,仍然需要双重检验来确认是否需要新建一个线程。
    (因为可能在上一次检查后,已经存在的线程已经died)或者进入这个方法后,线程池已经被关闭了。所以我们需要再次检查state,如果线程池停止了需要回滚入队列,如果池中没有线程了,新建一个线程。
  3. 如果不能把任务加入队列(可能线程池已经关闭或者满了),那么需要新开一个线程(往maxPoolSize发展)。如果失败的话,说明线程池shutdown了或者满了,就要拒绝这个任务了。



53748a021ebe1af2ecfedad6b67f3f60.jpeg



3b7f3bf4e687cab23de86706709d8659.jpeg


给你流程图!


73b419d23adf7bfe676c7bfca94d10d8.jpeg



e4c6f247f60701a22f71362ca66e44e3.jpeg



6ead366a152df6f65d1b015ed91850f8.jpeg




fa1fe95f67e5641b48b80ee471bed6f4.jpeg


15f28fd757fe1bf3829d121a862a9ee1.jpeg


8c8b688ecc1391eab9f94ec3f6320939.jpeg






a82ffcc4e62379099df506bde47a1dd6.jpeg



46c3e8e7087f744b6a3458c179a42862.jpeged4a10a6a6afacf5efaa0864b9915c2d.jpeg





工具类 Executors


线程池里还有个重要的类:Executors~

Executors是一个Java中的工具类,它提供工厂方法来创建不同类型的线程池。

用它可以很方便地创建出下面几种线程池来~

0148be02281f2041ff6b6b5c623151df.jpeg66d908a0f310d60e1cc8c641d3995d99.png

或者通过ThreadPoolExecutor的构造函数自定义需要的线程池。



c5b91a5853b8a95561a8b614eff82b76.jpeg



02158b377ad97ed442358bfd5bfbeae7.jpeg


猜你喜欢

转载自blog.51cto.com/15050718/2633305