(一)由标准线程池ThreadPoolExecutor演变而来的四大线程池
(1)FixedThreadPool是一种可重用固定线程数的线程池,阻塞队列适用的是LinkedBlockingQueue的无界阻塞队列,适用于需要保证所有提交的任务都要被执行的情况
(2)SingleThreadExecutor是使用单个worker线程的Executor,也就是他的corePoolSize和maximumPoolSize都被设置为1,而且阻塞队列适用的也是无界限的LinkedBlockingQueue,适用于适用于业务逻辑上只允许1个线程进行处理的场景
(3)CachedThreadPool是一个会根据需要创建新线程的线程池,corePoolSize被设置为0,即corePool为空;maximumPoolSize被设置为Integer.MAX_VALUE,即maximumPool是无界的,适用于如果希望提交的任务尽快分配线程执行,就可以适用这个
(4)ScheduledThreadPoolExecutor会把待调度的任务(ScheduledFutureTask)
放到一个DelayQueue中,而且DelayQueue是一个在PriorityQueu基础上封装之后的一个阻塞队列,然后线程池会先根据谁的线程任务先到期,然后会先去执行那个任务,如果是两个任务都是同时到期的话,会再去根据任务序号去判断先去执行哪个定时任务
(5)当然除此之外,我们还可以根据标准线程池ThreadPoolExecutor的参数来进行自定义的设置针对不同的业务来进行个性化的设计,前提是我们要十分了解线程池的几个参数
(1)FixedThreadPool的实现原理
- 如果当有一个线程被提交执行的时候,就会先看corePool线程池的是不是已经满了,如果没有满的时候就会直接创建一个线程来执行任务
- 如果corePool核心线程池已经满的时候,此时只能把被提交的线程放到LinkedBlockingQueue这个无界阻塞队列中等待
- 如果corePool线程池中的线程要是有完成的任务之后,就会继续向LinkedBlockingQueue队列中去取排在前面的线程去执行任务,然后就会一直反复循环这个从第一步到第三步这个过程
如果使用这个线程池会出现的一些缺点: - 当线程池中的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池中的线程数不会超过corePoolSize。
- 无界队列时maximumPoolSize将是一个无效参数
- 使用无界队列时keepAliveTime将是一个无效参数
- 由于使用无界队列,运行中的FixedThreadPool(未执行方法shutdown()或
shutdownNow())不会拒绝任务(不会调用RejectedExecutionHandler.rejectedExecution方法)。
(2)SingleThreadExecutor实现原理的详解
SingleThreadExecutor的corePoolSize和maximumPoolSize被设置为1。也就是说整个SingleThreadExecutor线程池中只会保证一直有一个线程在线程池中在执行,他用的也是LinkedBlockingQueue无界队列,他的缺点和上一个固定线程大小的线程池的缺点是一样的
- 也就是主线程如果有提交一个新的线程过来的话,他会先去看看SingleThreadExecutor线程池是不是有一个线程正再执行,如果没有一个线程的话,就会去直接执行刚刚提交过来的这个线程
- 如果已经有一个线程正在执行的话,就会吧这个线程放到无节限的对列中去执行
- 当corePool这个核心线程池中的线程执行完毕之后,他就会去从哪个LinkedBlockingQueue无界限对列中去取到一个线程继续来执行,然后依次反复执行
(3)CachedThreadPool实现原理的详解
这里先来了解一下行阻塞队列SynchronousQueue是个什么玩意?
- 首先SynchronousQueue没有容量。与其他BlockingQueue不同,SynchronousQueue是一个不存储元素的BlockingQueue。每一个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然。
- SynchronousQueue分为公平和非公平,默认情况下采用非公平性访问策略,当然也可以通过构造函数来设置为公平性访问策略(为true即可)
- 因为没有容量,所以对应 peek, contains, clear, isEmpty … 等方法其实是无效的。例如clear是不执行任何操作的,contains始终返回false,peek始终返回null。
下面说一下他的实现原理的流程:
- 首先主线程会先执行excute()方法,把这个提交的待执行的线程放到SynchronousQueue这个阻塞队列中(他就好比像一个中间传递员,如果A【代表主线程】给SynchronousQueue一个包裹,但是SynchronousQueue这里不能存放包裹公司规定)此时SynchronousQueue会等待着B【也就是来取包裹的用户】来取包裹,此时只有当B去问SynchronousQueue说有没有人给我包裹啊?如果有赶紧给我,我只能等待keepAliveTime【自己定义的时间】这么久的时间啊,不然我就走了)
- 如果最开始的时候当maximumPool中没有有空闲线程的时候,当然这样是永远都不会有人向SynchronousQueue询问有没有提交的线程过来,这种情况下就会失败,此时CachedThreadPool会创建一个新线程执行任务。
- 当第二步中已经创建好了一个新的空闲线程的时候,此时会再去向SynchronousQueue调用poll()方法去问SynchronousQueue有没有主线程提供任务分配给我,如果有就直接给他执行了,如果没有的话,刚刚新创建好的一个线程会在定义的参数时间后销毁,然后循环往复进行。
(4)ScheduledThreadPoolExecutor定时线程池的工作原理
- 先来了解一下ScheduledThreadPoolExecutor的适用场景和JDK提供的Timer
首先Timer简单易用,但所有任务都是由同一个线程来调度,任务串行执行,任务之间存在互相干扰,一是前一个任务的延迟会导致后面的任务延迟,二是前一个任务异常导致后面的任务不再执行,三是Timer执行周期任务时依赖系统时间,如果当前系统时间发生变化,执行行为也会出现变化
这个是Timer和ScheduledThreadPoolExecutor的对比图
ScheduledThreadPoolExecutor的运行原理图
- 首先主线程会先提交过来一个定时调度任务提交到DelayQueue这个阻塞队列中,如果ScheduledThreadPoolExecutor线程池中有空闲的线程就会去执行个ScheduledFutureTask里面的到期任务
- 在执行完事之后,线程1修改ScheduledFutureTask的time变量为下次将要被执行的时间
- 线程1把这个修改time之后的ScheduledFutureTask放回DelayQueue中(DelayQueue.add())。