(八)如何理解Executors框架下的四大线程池运行原理和适用场景

(一)由标准线程池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的实现原理

(八)如何理解Executors框架下的四大线程池运行原理和适用场景_第1张图片

  1. 如果当有一个线程被提交执行的时候,就会先看corePool线程池的是不是已经满了,如果没有满的时候就会直接创建一个线程来执行任务
  2. 如果corePool核心线程池已经满的时候,此时只能把被提交的线程放到LinkedBlockingQueue这个无界阻塞队列中等待
  3. 如果corePool线程池中的线程要是有完成的任务之后,就会继续向LinkedBlockingQueue队列中去取排在前面的线程去执行任务,然后就会一直反复循环这个从第一步到第三步这个过程
    如果使用这个线程池会出现的一些缺点:
  4. 当线程池中的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池中的线程数不会超过corePoolSize。
  5. 无界队列时maximumPoolSize将是一个无效参数
  6. 使用无界队列时keepAliveTime将是一个无效参数
  7. 由于使用无界队列,运行中的FixedThreadPool(未执行方法shutdown()或
    shutdownNow())不会拒绝任务(不会调用RejectedExecutionHandler.rejectedExecution方法)。

(2)SingleThreadExecutor实现原理的详解

(八)如何理解Executors框架下的四大线程池运行原理和适用场景_第2张图片

SingleThreadExecutor的corePoolSize和maximumPoolSize被设置为1。也就是说整个SingleThreadExecutor线程池中只会保证一直有一个线程在线程池中在执行,他用的也是LinkedBlockingQueue无界队列,他的缺点和上一个固定线程大小的线程池的缺点是一样的

  1. 也就是主线程如果有提交一个新的线程过来的话,他会先去看看SingleThreadExecutor线程池是不是有一个线程正再执行,如果没有一个线程的话,就会去直接执行刚刚提交过来的这个线程
  2. 如果已经有一个线程正在执行的话,就会吧这个线程放到无节限的对列中去执行
  3. 当corePool这个核心线程池中的线程执行完毕之后,他就会去从哪个LinkedBlockingQueue无界限对列中去取到一个线程继续来执行,然后依次反复执行

(3)CachedThreadPool实现原理的详解

(八)如何理解Executors框架下的四大线程池运行原理和适用场景_第3张图片

这里先来了解一下行阻塞队列SynchronousQueue是个什么玩意?

  1. 首先SynchronousQueue没有容量。与其他BlockingQueue不同,SynchronousQueue是一个不存储元素的BlockingQueue。每一个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然。
  2. SynchronousQueue分为公平和非公平,默认情况下采用非公平性访问策略,当然也可以通过构造函数来设置为公平性访问策略(为true即可)
  3. 因为没有容量,所以对应 peek, contains, clear, isEmpty … 等方法其实是无效的。例如clear是不执行任何操作的,contains始终返回false,peek始终返回null。

下面说一下他的实现原理的流程:

  1. 首先主线程会先执行excute()方法,把这个提交的待执行的线程放到SynchronousQueue这个阻塞队列中(他就好比像一个中间传递员,如果A【代表主线程】给SynchronousQueue一个包裹,但是SynchronousQueue这里不能存放包裹公司规定)此时SynchronousQueue会等待着B【也就是来取包裹的用户】来取包裹,此时只有当B去问SynchronousQueue说有没有人给我包裹啊?如果有赶紧给我,我只能等待keepAliveTime【自己定义的时间】这么久的时间啊,不然我就走了)
  2. 如果最开始的时候当maximumPool中没有有空闲线程的时候,当然这样是永远都不会有人向SynchronousQueue询问有没有提交的线程过来,这种情况下就会失败,此时CachedThreadPool会创建一个新线程执行任务。
  3. 当第二步中已经创建好了一个新的空闲线程的时候,此时会再去向SynchronousQueue调用poll()方法去问SynchronousQueue有没有主线程提供任务分配给我,如果有就直接给他执行了,如果没有的话,刚刚新创建好的一个线程会在定义的参数时间后销毁,然后循环往复进行。
    (八)如何理解Executors框架下的四大线程池运行原理和适用场景_第4张图片

(4)ScheduledThreadPoolExecutor定时线程池的工作原理

  1. 先来了解一下ScheduledThreadPoolExecutor的适用场景和JDK提供的Timer

首先Timer简单易用,但所有任务都是由同一个线程来调度,任务串行执行,任务之间存在互相干扰,一是前一个任务的延迟会导致后面的任务延迟,二是前一个任务异常导致后面的任务不再执行,三是Timer执行周期任务时依赖系统时间,如果当前系统时间发生变化,执行行为也会出现变化
这个是Timer和ScheduledThreadPoolExecutor的对比图
(八)如何理解Executors框架下的四大线程池运行原理和适用场景_第5张图片

ScheduledThreadPoolExecutor的运行原理图
(八)如何理解Executors框架下的四大线程池运行原理和适用场景_第6张图片
(八)如何理解Executors框架下的四大线程池运行原理和适用场景_第7张图片

  1. 首先主线程会先提交过来一个定时调度任务提交到DelayQueue这个阻塞队列中,如果ScheduledThreadPoolExecutor线程池中有空闲的线程就会去执行个ScheduledFutureTask里面的到期任务
  2. 在执行完事之后,线程1修改ScheduledFutureTask的time变量为下次将要被执行的时间
  3. 线程1把这个修改time之后的ScheduledFutureTask放回DelayQueue中(DelayQueue.add())。

你可能感兴趣的:(【并发专题】)