线程-线程池
认识ScheduledThreadPoolExecutor前言
concurrent包为我们提供了多种的线程池,其他的几种都是比较常规的,ScheduledThreadPoolExecutor比较特殊,我们就拿它出来学习学习。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210116144647582.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2c2MjU2NjEz,size_16,color_FFFFFF,t_70#pic_center)提示:以下是本篇文章正文内容
一、ScheduledThreadPoolExecutor是什么?
ScheduledThreadPoolExecutor继承ThreadPoolExecutor对execute和submit进行了重写,
同时也实现了ScheduledExecutorService特有的方法。
其主要的目的就是为了实现周期性执行任务或给定时间延后执行异步任务。
二、类的实现与继承
1.两个重要的内部类
-
DelayWorkQueue
主要实现了阻塞队列的接口,可以看出他是一个专门定制的阻塞队列,
-
ScheduledFutureTask
ScheduledFutureTask具有FutureTask类的所有功能,并实现了RunnableScheduledFuture接口的所有方法。
ScheduledFutureTask类的定义如下所示
三. 构造方法
//最大线程数是Integer.MAX_VALUE,理论上是一个无限大的线程池
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), handler);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory, handler);
}
四. 独有方法
//创建并执行一次操作,该操作在给定的延迟后启用。
public ScheduledFuture<?> schedule(Runnable command,
long delay, TimeUnit unit);
//这里传入的是实现Callable接口的任务
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay, TimeUnit unit);
//执行时间将在initialDelay+period后开始执行
//进行检测上一次任务,上次一任务执行完毕,下一个任务才会执行
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
//initialDelay时间之后开始执行周期性任务
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
五. 实践
我们要特别注意异常情况下,会打断定时任务。
public static void schedule(ScheduledExecutorService executorService){
executorService.schedule(() ->{
for (int i= 0 ; i <10 ;i++) {
executorService.submit(() -> {
System.out.println("CurrentThread name:" + Thread.currentThread().getName() + "date:" + Instant.now());
});
}
},5, TimeUnit.SECONDS);
}
public static void scheduleCall(ScheduledExecutorService executorService){
ScheduledFuture scheduledFuture = executorService.schedule(() -> "scheduleCall",1L,TimeUnit.SECONDS);
try {
System.out.println(scheduledFuture.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
public static void scheduleAtFixedRate(ScheduledExecutorService executorService) {
ScheduledFuture scheduledFuture = executorService.scheduleAtFixedRate(() -> {
for (int i = 0; i < 10; i++) {
executorService.submit(() -> {
System.out.println("CurrentThread name:" + Thread.currentThread().getName() + "date:" + Instant.now());
});
}
}
, 0, 5L, TimeUnit.SECONDS);
try {
System.out.println(scheduledFuture.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
//针对异常情况,会打断定时任务的执行
public static void scheduleWithFixedDelay(ScheduledExecutorService executorService) {
ScheduledFuture scheduledFuture = executorService.scheduleWithFixedDelay(() -> {
try {
int j = 1 / 0;
} catch (Exception e) {
e.printStackTrace();
}
}
, 10, 5L, TimeUnit.SECONDS);
}
针对于死锁扫描的场景,我们也是可以用到它的
ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
Runnable dlCheck = () -> {
long[] threadIds = mbean.findDeadlockedThreads();
if (threadIds != null) {
ThreadInfo[] threadInfos = mbean.getThreadInfo(threadIds);
System.out.println("Detected deadlock threads:");
for (ThreadInfo threadInfo : threadInfos) {
System.out.println(threadInfo.getThreadName()+"11111");
}
}
};
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
// 稍等 5 秒,然后每 10 秒进行一次死锁扫描
scheduler.scheduleAtFixedRate(dlCheck, 5L, 10L, TimeUnit.SECONDS);
// 死锁样例代码…
通过每10秒扫描一次,打印出死锁的线程,这是我们可以利用在实际生产中,对于线程的简单监控。
六. 源码环节
参数说明:
参数 | 含义 |
---|---|
command | 要执行的任务 |
delay | 延迟时间 |
unit | 时间单位 |
schedule 源码:
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
//将提交的任务转换成ScheduledFutureTask,如果period为true则是周期性任务
RunnableScheduledFuture<?> t = decorateTask(command,
new ScheduledFutureTask<Void>(command, null,
triggerTime(delay, unit)));
//任务的主要执行方法
delayedExecute(t);
return t;
}
ScheduledThreadPoolExecutor主要就是完成延时或周期性的任务,delayedExecute方法就是主要的实现,
最后通过ensurePrestart方法,添加线程启动任务。
private void delayedExecute(RunnableScheduledFuture<?> task) {
if (isShutdown())
//如果线程池已经停止,则调用拒绝策略
reject(task);
else {
//将任务添加入Worker阻塞队列
super.getQueue().add(task);
if (isShutdown() &&
!canRunInCurrentRunState(task.isPeriodic()) &&
remove(task))
//取消并删除任务
task.cancel(false);
else
//至少启动一个线程去执行
ensurePrestart();
}
}
void ensurePrestart() {
//通过底29位获取线程池中的线程数量
int wc = workerCountOf(ctl.get());
if (wc < corePoolSize)
addWorker(null, true);
else if (wc == 0)
addWorker(null, false);
}
又看到我们熟悉的addWorker方法,我们可以翻阅前面的文章来了解下主要的执行流程。
addWorker的主要流程:
- 检查线程池状态
- 新建线程,使用Worker进行包装,放入Hashet数组中,最终真正执行任务的线程就放在Worker,所以新增一个addWorker就是新增一个线程。主要实现复用就是Worker类中的runWorker(this)
- 启动线程Start()
- 添加失败操作,移除Worker,减少WorkerCount
七.总结
主要的三个对象
执行者:Worker
任务:ScheduledFutureTask
执行结果:ScheduledFuture
流程:
- 将提交的任务转换成ScheduledFutureTask
- 将ScheduledFutureTask添加到Worker队列中
- 调用addWorker方法,调用它的核心方法runWorker
- 调用getTask方法从阻塞队列中不断的去获取任务进行执行,直到从阻塞队列中获取的任务为 null 的话,线程结束终止