Scheduled时间调度是什么意思
本篇内容介绍了“Scheduled时间调度是什么意思”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
创新互联公司2013年开创至今,先为凭祥等服务建站,凭祥等地企业,进行企业商务咨询服务。为凭祥企业网站制作PC+手机+微官网三网同步一站式服务解决您的所有建站问题。
Schedule,可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多,它是spring团队开发的任务调度插件,它可以按照我们设计的时间周期执行既定的任务,首先来一个串行的 Schedule设计:
第一步: 在自定义类中添加注解@EnableScheduling,启动scheduling,具体代码如下
@SpringBootApplication @MapperScan("com.xash.quartzDemo.mapper") @EnableSwagger2 @EnableScheduling public class SpringbootStartApplication { public static void main(String[] args) { SpringApplication.run(SpringbootStartApplication.class, args); } }
第二步:创建一个类,并注入到spring中,让该类实现SchedulingConfigurer,并重写configureTasks方法,这样可以实现基于多任务下的,多线程任务定都执行方案:
具体代码如下
package com.xash.quartzDemo.config; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.Executors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import org.springframework.stereotype.Component; import com.serotonin.modbus4j.exception.ErrorResponseException; import com.serotonin.modbus4j.exception.ModbusInitException; import com.serotonin.modbus4j.exception.ModbusTransportException; import com.xash.quartzDemo.collection.utils.Modbus4jUtil; @Component public class TaskConfiguration implements SchedulingConfigurer { @Autowired private Modbus4jUtil Modbus4jUtil; private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Scheduled(fixedRate=100,initialDelay=10000) public void myTask() throws InterruptedException, ModbusTransportException, ErrorResponseException, ModbusInitException { System.out.println("当前系统时间0:"+sdf.format(new Date())); String name=Thread.currentThread().getName(); System.out.println("当前执行线程"+name); } @Scheduled(fixedDelayString="${com.test.scheduled}") public void myTask1() throws InterruptedException { System.out.println("当前系统时间1:"+sdf.format(new Date())); String name=Thread.currentThread().getName(); System.out.println("当前执行线程"+name); } @Scheduled(cron = "0/10 * * * * ?") // 每2秒钟执行一次 public void myTask2() throws InterruptedException { System.out.println("当前系统时间2:"+sdf.format(new Date())); String name=Thread.currentThread().getName(); System.out.println("当前执行线程"+name); /* throw new RuntimeException("运行时异常");*/ } @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(Executors.newScheduledThreadPool(5)); } /* @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(taskExecutor()); } @Bean(destroyMethod="shutdown") public Executor taskExecutor() { return Executors.newScheduledThreadPool(15); //指定线程池大小 }*/ }
Executors该接口时Java线程池的顶级接口,具体Java线程池的原理分析如下:
JDK1.8中的ThreadPoolExecutor分析线程池对象的依赖关系:
Executor:执行提交的线程任务的对象。这个接口提供了一种将任务提交与每个任务将如何运行实现了分离,包括线程使用、调度等细节。该接口只定义了一个execute()方法。
ExecutorService
提供用于管理终止的方法如 shutDown()和shutDownNow()用于关闭线程池的方法以及判断线程池是否关闭的方法如,isShutdown(),isTerminated()的方法
提供了可以生成用于跟踪一个或多个异步任务进度的方法如,invokeAll(),submit()。这些方法的返回值都是Future类型,可以获取线程的执行结果。
ThreadPoolExecutor成员变量
ctl是对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段,ctl是一个Integer, 它包含两部分的信息: 高三位表示线程池的运行状态 (runState) 和低29位表示线程池内有效线程的数量 (workerCount),
线程池的生命周期,总共有五种状态
RUNNING :能接受新提交的任务,并且也能处理阻塞队列中的任务;
SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。(finalize() 方法在执行过程中也会调用shutdown()方法进入该状态);
STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态;
TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。
TERMINATED:在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做。
进入TERMINATED的条件如下:
线程池不是RUNNING状态;
线程池状态不是TIDYING状态或TERMINATED状态;
如果线程池状态是SHUTDOWN并且workerQueue为空;
workerCount为0;
设置TIDYING状态成功。
还有三个关于ctl的方法
runStateOf:获取运行状态;
workerCountOf:获取活动线程数;
ctlOf:获取运行状态和活动线程数的值
ThreadPoolExecutor构造函数
下面解释构造函数的参数含义
corePoolSize:核心线程数量,当有新任务在execute()方法提交时,会执行以下判断:
a):如果运行的线程少于 corePoolSize,则创建新线程来处理任务,即使线程池中的其他线程是空闲的;
b):如果线程池中的线程数量大于等于 corePoolSize 且小于 maximumPoolSize,当workQueue未满的时候任务添加到workQueue中,当workQueue满时才创建新的线程去处理任务;
c):如果设置的corePoolSize 和 maximumPoolSize相同,则创建的线程池的大小是固定的,这时如果有新任务提交,若workQueue未满,则将请求放入workQueue中,等待有空闲的线程去从workQueue中取任务并处理;
d):如果运行的线程数量大于等于maximumPoolSize,这时如果workQueue已经满了,则通过handler所指定的策略来处理任务;
所以,任务提交时,判断的顺序为 corePoolSize –> workQueue –> maximumPoolSize。
maximumPoolSize:最大线程数量;
workQueue:等待队列,当任务提交时,如果线程池中的线程数量大于等于corePoolSize的时候,把该任务封装成一个Worker对象放入等待队列;
workQueue:保存等待执行的任务的阻塞队列,当提交一个新的任务到线程池以后, 线程池会根据当前线程池中正在运行着的线程的数量来决定对该任务的处理方式,主要有以下几种处理方式:
直接切换:这种方式常用的队列是SynchronousQueue。
使用无界队列:一般使用基于链表的阻塞队列LinkedBlockingQueue。如果使用这种方式,那么线程池中能够创建的最大线程数就是corePoolSize,而maximumPoolSize就不会起作用了。当线程池中所有的核心线程都是RUNNING状态时,这时一个新的任务提交就会放入等待队列中。
使用有界队列:一般使用ArrayBlockingQueue。使用该方式可以将线程池的最大线程数量限制为maximumPoolSize,这样能够降低资源的消耗,但同时这种方式也使得线程池对线程的调度变得更困难,因为线程池和队列的容量都是有限的值,所以要想使线程池处理任务的吞吐率达到一个相对合理的范围,又想使线程调度相对简单,并且还要尽可能的降低线程池对资源的消耗,就需要合理的设置这两个数量。
如果要想降低系统资源的消耗(包括CPU的使用率,操作系统资源的消耗,上下文环境切换的开销等), 可以设置较大的队列容量和较小的线程池容量, 但这样也会降低线程处理任务的吞吐量。
如果提交的任务经常发生阻塞,那么可以考虑通过调用 setMaximumPoolSize() 方法来重新设定线程池的容量。
如果队列的容量设置的较小,通常需要将线程池的容量设置大一点,这样CPU的使用率会相对的高一些。但如果线程池的容量设置的过大,则在提交的任务数量太多的情况下,并发量会增加,那么线程之间的调度就是一个要考虑的问题,因为这样反而有可能降低处理任务的吞吐量。
keepAliveTime:线程池维护线程所允许的空闲时间。当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime;
threadFactory:它是ThreadFactory类型的变量,用来创建新线程。默认使用Executors.defaultThreadFactory() 来创建线程。使用默认的ThreadFactory来创建线程时,会使新创建的线程具有相同的NORM_PRIORITY优先级并且是非守护线程,同时也设置了线程的名称。
handler:它是RejectedExecutionHandler类型的变量,表示线程池的饱和策略。如果阻塞队列满了并且没有空闲的线程,这时如果继续提交任务,就需要采取一种策略处理该任务。线程池提供了4种策略:
AbortPolicy:直接抛出异常,这是默认策略;
CallerRunsPolicy:用调用者所在的线程来执行任务;
DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
DiscardPolicy:直接丢弃任务;
线程池中的核心线程和非核心线程,没有什么区别,都是线程,只不过人为的规则线程池中的一部分线程叫核心线程
线程池的流程
ThreadPoolExecutor执行execute方法分下面4种情况。
1)如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。
2)如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。
3)如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁)。
4)如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用
RejectedExecutionHandler.rejectedExecution()方法。
ThreadPoolExecutor采取上述步骤的总体设计思路,是为了在执行execute()方法时,尽可能地避免获取全局锁(那将会是一个严重的可伸缩瓶颈)。在ThreadPoolExecutor完成预热之后当前运行的线程数大于等于corePoolSize),几乎所有的execute()方法调用都是执行步骤2,而步骤2不需要获取全局锁。
execute()源码分析
为什么线程池中的线程可以重复利用?
我们知道线程在执行完run()方法里面的逻辑后就会被GC回收,那么线程池是怎样保持线程的存活,并且重复利用线程。
在线程池中使用Worker类来包装向线程池中添加的Runnable线程任务。首先来分析一下addWorker()方法
在execute()方法中使用addWorker()方法的地方只有在添加核心线程和非核心线程的时候调用。
addWorker源码分析
从上面可以看出这是一个添加线程的过程,并没有看到,线程池是如何维护线程不被销毁,从而达到重复利用的
从addWorker()中我们可以看到,向线程池中添加的Runnable被包装成Worker对象,下面就来查看Worker对象,从中寻找为什么线程池中的线程可以重复利用的答案。
Worker源码分析
Worker类继承Runnable,和AbstractQueuedSynchronizer(这个类奠定了Java并发包的基础,很重要,可是我还没有深入研究)
查看run()方法,调用runWorker,并将自身作为参数
查看runWorker(),在前面的addWorker()方法在最后是执行了start()方法,也就是Worker的run()方法,进而执行了runWorker()方法。
这个while就是线程池中线程不被销毁的原因所在,在Worker的run方法中,如果while一直执行下去,那么Worker这个继承了Runnable接口的线程就会一直执行下去,而我们知道线程池中任务的载体是Worker,如果Worker一直执行下去,就表示该载体可以一直存在,换的只是载体上我们通过execute()方法添加的Runnable任务而已。
那么如何保证while方法一直执行下去
在第一次执行完while后task设置为null,那么就要保证task=getTask()!=null
查看getTask(),从名字可以看出这是获取一个任务。
通过代码中的注释,我们这就弄明白线程池的工作原理 以及线程池中如何保证线程(Worker)重复利用,不被销毁。
Executors创建线程池的种类:
1.固定数量线程池(newFixedThreadPool)
创建使用固定线程数的FixedThreadPool,适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景,它适用于负载比较重的服务器。
Executors构造newFixedThreadPool方式
查看源码
corePoolSize = maximumPoolSize =初始化的参数
workQueue:使用无界队列LinkedBlockingQueue链表阻塞队列
keepAliveTime = 0 由于使用无界队列LinkedBlockingQueue作为缓存队列,所以当corePoolSize满后,后面添加的线程任务都会添加到LinkedBlockingQueue中去,所以maximumPoolSize 就失去了意义,这样也就没有必要设置空闲时间
使用无界队列的影响,这也是为什么使用Eexcutors来创建线程池存在一定风险的原因
1)当线程池中的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池中的线程数不会超过corePoolSize。
2)使用无界队列时maximumPoolSize将是一个无效参数。
3)使用无界队列时keepAliveTime将是一个无效参数。
4)由于使用无界队列,运行中的FixedThreadPool(未执行方法shutdown()或shutdownNow())不会拒绝任务(不会调用RejectedExecutionHandler.rejectedExecution方法)。
代码实例
结果:
为什么线程名称会重复:这正是线程池的原理,因为线程池会重复利用已创建的线程,当一个任务Runnable被挂载到线程池中的一个线程,这个任务执行完毕后,会有另一个任务继续挂载到这个线程上面,所以会出现线程名称重复。
单例线程池(newSingleThreadExecutor)
适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景。
Executors构造newSingleThreadExecutor方式
源代码
corePoolSize = maximumPoolSize =1 由于是单例线程池,所以线程池中是有一个重用的线程
workQueue:使用无界队列LinkedBlockingQueue链表阻塞队列
keepAliveTime:0 原因上面已经阐述
代码实例
结果
只有一个可重用的线程,任务的执行顺序和添加顺序一致
缓存线程池(newCachedThreadPool)
创建一个会根据需要创建新线程的,适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器。
Executors构造newCachedThreadPool方式
源代码
corePoolSize:0 表示线程池中没有核心线程,都是非核心线程
maximumPoolSize :线程池容量Integer最大值
keepAliveTime:60秒 由于没有核心线程的存在,线程池中创建的线程都是非核心线程,所以设置空闲时间60秒,当非核心线程60秒后没有被重用,将会被销毁,如果没有线程提交给该线程池,超过空闲时间,该线程池就没有非空闲线程,那么该线程池也就不会消耗过多的资源,
workQueue:SynchronousQueue是一个不存储元素的阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加元素。
代码实例
结果
定时线程池(newScheduledThreadPool)
它主要用来在给定的延迟之后运行任务,或者定期执行任务,例如定时轮询数据库中的表的数据
Executors构造newScheduledThreadPool方式
workQeueu:delayWorkQueue,使用延迟队列作为缓存队列
任务提交方式
schedule(Callable
callable:提交Callable或者Runnable任务
delay:延迟时间
unit:时间级别
该方法表示在给定的delay延迟时间后执行一次,有返回结果
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);
command:提交Runnable任务
initialDelay:初始延迟时间
period:表示连个任务连续执行的时间周期,第一个任务开始到第二个任务的开始,包含了任务的执行时间
unit:时间级别
该方法在initialDelay时间后开始周期性的按period时间间隔执行任务
scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit);
command:提交Runnable任务
initialDelay:初始延迟时间
delay:表示延迟时间 第一个任务结束到第二个任务开始的时间间隔
unit:时间级别
单例延迟线程池(newSingleThreadScheduledExecutor)
Executors构造newSingleThreadScheduledExecutor方式
corePoolSize :1由于是单例线程池,所以核心线程为1,线程池中只有一个重用线程
总结:利用sping的线程调度结合java的线程池可以实现多线程的任务调度
“Scheduled时间调度是什么意思”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注创新互联网站,小编将为大家输出更多高质量的实用文章!
当前名称:Scheduled时间调度是什么意思
分享网址:http://myzitong.com/article/jhicig.html