一、前言
本文来讲解遇到的一个有意思的与定时器相关的生产消费模型,模型如下图:
生产者是一个定时器线程,使用ScheduledThreadPoolExecutor的scheduleAtFixedRate控制每间隔3s投递一个元素到队列1,2,3(使用offer方法)。比如第1s放入一个元素到队列1,2,3,这时候每个队列里面有一个元素。然后第4s在放入一个元素到队列1,2,3,如果没有消费线程的时候这时候每个队列里面有2个元素.
队列为有界阻塞队列(ArrayBlockingQueue),队列元素的大小设置为6个元素。
消费者是一个定时器线程,使用ScheduledThreadPoolExecutor的scheduleAtFixedRate控制每间隔1s轮询取出每个队列里面全部元素(使用poll方法)。比如第1s取出队列1里面的全部元素,第2s取出队列2里面的元素,第3s取出队列3里面的元素,第4s取出队列1里面的元素....
并且如果发现当前队列为空则会结束当前s的任务,然后等下1s到了的时候从下一个队列开始取。消费线程启动后,生产线程才启动,生产者和消费者线程优先级相同
消费线程和生产线程里面没有耗时操作,或者耗时都不超过1s。这个保证定时器任务不会被延迟执行。
问题:每个队列里面最多时候会有几个元素?
二、分析
- (1)假设第1s时候消费线程去获取第一个队列元素,这时候第一个队列为空,则当前任务结束,消费线程等到第2s时候会去第二个队列取元素。
- (2)假设第1.000000000001s时候生产者线程放入元素到每个队列,这时候每个队列有一个元素
- (3)第2s时候消费者线程去获取第二个队列元素,里面有一个元素,取出后,队列为空
- (4)第3s时候消费者线程去获取第三个队列元素,里面有一个元素,取出后,队列为空
- (5)第4s时候消费者线程去获取第一个队列元素,里面有一个元素,取出后,队列为空。
- (6)第4.000000000001s时候生成者线程放入元素到每个队列,这时候每个队列元素为1个。
按照上面的逻辑看的话,每个队列里面最多有一个元素。其实不然,因为在多线程模型中每个线程占用cpu执行的时间是按照时间片来划分的,每个线程执行完自己的时间片后会被挂起,然后下一个获取到时间片的线程会占用CPU执行自己的任务,当下一轮被挂起的线程获取到自己的时间片后,会恢复执行上下文从之前被挂起的地方执行。
所以这里步骤(6)并不能保证比步骤(5)先执行,有可能消费线程在执行步骤(5)前时间片用完了,则这时候消费线程会被挂起,而如果现在生产者线程获取到了cpu并且到达了定时执行任务的时间点,则步骤(6)会执行,那么这时候队列一,里面会有2个元素,那么等消费线程获取CPU时间片执行时候会从队列1里面拿到2个元素。
注:这里使用1.000000000001s是为了说明和1s比较接近,其实由于影响调度因素很多,有可能有比这更接近1s的时间
三、总结
多线程下会遇到很多微妙的情况,有时候遇到的问题要结合OS的知识才能解释清楚,本节从其中一个角度分析了每个队列里面有可能会有两个元素的原因,欢迎大家补充其他原因,并考虑会不会存在一个队列里面最多时候有3个元素的情况