一、ScheduledThreadPoolExecutor

并发文章第一篇学习的 ThreadPoolExecutor 是 java 的普通线程池,而 ScheduledThreadPoolExecutor 是 java 提供的定时任务线程池。

1、常用方法

java.util.concurrent.ScheduledThreadPoolExecutor#schedule 定时任务
java.util.concurrent.ScheduledThreadPoolExecutor#scheduleAtFixedRate 固定速率连续执行
java.util.concurrent.ScheduledThreadPoolExecutor#scheduleWithFixedDelay 非固定速率连续执行
java.util.concurrent.ScheduledThreadPoolExecutor.DelayedWorkQueue 延迟队列

2、源码解析

其实定时线程池底层大致和普通线程池差不多,普通线程池的getTask随时都能将task拿出来,但是延迟队列的getTask只能在规定时间之后才能从队列中取出来。因为ScheduledThreadPoolExecutor是 ThreadPoolExecutor的子类,相当于子类对父类的一些功能进行了包装。

image.png

2.1、ScheduledExecutorService#scheduleAtFixedRate
实际由 ScheduledThreadPoolExecutor#scheduleAtFixedRate 来实现该方法。
image.png
先用 if 判断,然后调用 ScheduledThreadPoolExecutor.ScheduledFutureTask 的构造函数得到一个 sft对象。

2.2、ScheduledThreadPoolExecutor#delayedExecute
image.png

2.3、ThreadPoolExecutor#ensurePrestart
image.png

上面几个步骤,就是封装成ScheduledFutureTask,将task放到队列里面去,然后去addWork(new Work),启动那个work(getTask)。

  1. java.util.concurrent.ScheduledThreadPoolExecutor.scheduleAtFixedRate
  2. >java.util.concurrent.ScheduledThreadPoolExecutor.ScheduledFutureTask
  3. >java.util.concurrent.ScheduledThreadPoolExecutor.decorateTask
  4. >java.util.concurrent.ScheduledThreadPoolExecutor.delayedExecute
  5. >java.util.concurrent.ThreadPoolExecutor.ensurePrestart
  6. >java.util.concurrent.ThreadPoolExecutor.addWorker

任务是怎么放到队列里面去的?
2.4、ScheduledThreadPoolExecutor.DelayedWorkQueue.add
DelayedWorkQueue 是ScheduledThreadPoolExecutor 类中新定义的类,add 方法中调用了 offer方法。
image.png

ScheduledThreadPoolExecutor.DelayedWorkQueue#offer,该方法是使用了二叉树堆排序算法给任务的延迟时间进行排序。
image.png

java.util.concurrent.ScheduledThreadPoolExecutor.ScheduledFutureTask#run

2.5、ScheduledThreadPoolExecutor.DelayedWorkQueue#siftUp
在上面的 offer 方法中调用了 siftUp 方法来对任务进行排序;
image.png
其中调用 compareTo 方法 去判断时间。

2.6、ScheduledThreadPoolExecutor.DelayedWorkQueue#siftDown
当一波任务完成之后,再调用 siftDown 方法再排序。
image.png

3、scheduleAtFixedRate 和 scheduleWithFixedDelay

两个区别就是传参时,With的那个方法把传入的间隔时间参数设置为负数。

scheduleAtFixedRate 是固定速率连续执行:
1、任务1执行需2s,定时每3s执行该任务,所以任务执行完要等1s再继续执行;
2、任务1执行需4s,定时每3s执行该任务,过了定时时间也不能再执行,需要等上个任务完成,存在阻塞。

scheduleWithFixedDelay 非固定速率连续执行:
1、不管你的任务时间要执行多久,都是等你执行完我再等上间隔时间再去执行该任务。

原因是With方法是传参时,设置为负数时间,
image.png

ScheduledThreadPoolExecutor.ScheduledFutureTask#run,在它的run方法中调用了下一次的执行时间。
image.png

ScheduledThreadPoolExecutor.ScheduledFutureTask#setNextRunTime,At方法的下一次执行时间是在基础上加(任务超时就等等),但是With方法是负数,if判断不执行,去调用 triggerTime方法。
image.png

ScheduledThreadPoolExecutor#triggerTime(long),p是负数,传-p表示delay参数为正,会在now()上加间隔时间,就是任务执行结束时间上加间隔时间。
image.png

4、注意事项

job(执行的任务)里有异常一定要捕获,要然 job 执行一次就不执行了,即使定时也不执行了。

二、单列模式 DoubleCheckLock

双重锁定
发布与逸出这块以单列模式举例,在高并发下如何实现一个线程安全的单列模式?
1、懒汉模式;2、饿汉模式;3、synchronized;4、volatile;5、枚举;6 重排序;7、内部类;8、枚举类。
2、3、5、7、8可以做到线程安全。

三、AQS 同步器

AbstractQueuedSynchronizer,

四、Java多线程并发编程

脑图
https://www.processon.com/view/link/5b71947ce4b0be50eadcdad0#map