当前位置: 首页 > 后端技术 > Java

Quartz-SimpleThreadPool

时间:2023-04-01 19:54:21 Java

今天开始学习Quartz,一个企业级的作业调度框架。简单说明一下,Quartz的文档可以从官网:Quartztutorials获取,中文文档比较全面,不难找。如果你想练习或学习如何使用Quartz,参考这些文档很容易上手。我们将从另一个角度来学习和记录Quartz的学习过??程:尽可能从源码的角度理解Quartz的底层原理。作为一个企业级的作业调度框架,复杂度当然和JDKTimer不在一个数量级,Quartz也不是一蹴而就的,所以要有心理准备,循序渐进。Quartz的一个例子虽然Quartz的底层原理比较复杂,但是使用起来并不复杂。第一步:创建一个Job对象并实现execute方法。第2步:创建JobDetail。第3步:创建触发器。第四步:创建Scheduler,将JobDetail对象绑定到Scheduler中,启动Schedule@Slf4jpublicclassHelloJobimplementsJob{@Overridepublicvoidexecute(JobExecutionContextjobExecutionContext)throwsJobExecutionException{log.info("Idon'tknowwantshould我做..."+Thread.currentThread().getId());}publicstaticvoidmain(String[]args){JobDetailjobDetail=newJob(HelloJob.class).withDescription("Thisismyfirstquartzjob").withIdentity("MyJob").build();触发器trigger=newTrigger().withIdentity("myTriggger","MyGroup").startNow().withSchedule(simpleSchedule().withIntervalInSeconds(1).repeatForever()).build();尝试{Schedulersche=newStdSchedulerFactory().getScheduler();计划表工作(工作细节,触发器);sche.start();}catch(Exceptione){e.printStackTrace();}}运行结果:任务立即开始执行,每秒运行一次,符合预期从执行结果中也可以发现,每次运行前10个任务的线程id都不一样,从第11次,后续任务重复前10次的线程id。Quartz包含的主要对象Quartz包含的关键组件:Job:任务接口JobDetail:任务详情接口Trigger:触发器Schedule:任务调度器ScheduleThread:任务调度线程SimpleThreadPool:任务执行线程池WorkerThread:任务执行线程JobStore:任务存储Job&JobDetailJob是包含执行方法的任务接口。Job类似于JDKTimer中的TimerTask,是提供给应用程序实现任务逻辑的API。Quartz还提供了JobDetail接口。最终绑定到任务调度器的不是Job而是JobDetail。后面我们会简单分析其中的原因。JobDetail不需要由应用程序实现。Quartz提供了一个实现类JobDetailImpl。应用程序通过JoBuilder创建的JobDetail其实就是这个JobDetailImpl。JobDetailImpl将持有应用程序实现的Job实现类的类名,而不是直接持有Job实现类。目的。Trigger触发器有点类似于JDKTimer中的Timer对象,但又不完全相同。为了更大的灵活性,Quartz相当于将JDKTimer中的Timer对象拆分为两个对象,Trigger和Schedule。Triggle负责设置任务触发规则。有两个基本实现,SimpleTriggleImpl和CronTriggerImpl。SimpleTriggleImpl实现了相对简单的触发规则。CronTriggerImpl可以支持cron表达式,因此可以设置更复杂的任务触发规则。ScheduleSchedule是任务调度器,这部分应该是Quartz比较复杂的部分。任务调度器由StdSchedulerFactory创建,默认实现为StdScheduler。StdScheduler是代理模式的设计,它将所有的方法调用都委托给了它的属性QuartzScheduler,最终QuartzScheduler真正负责任务调度。从上面的示例代码中,我们其实可以发现,JobDetail和Trigger都绑定了Schedule,而Schedule实际上是绑定了QuartzScheduler,由QuartzSchedulerResources属性持有。QuartzScheduler的另一个重要属性是QuartzSchedulerThread,它是真正的任务调度器,是在QuartzScheduler初始化时创建的。下面说说这个QuartzSchedulerThread。QuartzSchedulerThreadQuartzSchedulerThread是Quartz的任务调度线程。事实上,Quartz流行的一个关键原因是它的线程管理机制。Quartz的任务调度线程和任务执行线程是分开的,这样他就可以避免JDKTimer的一个缺陷:任务执行影响任务的调度。QuartzSchedulerThread在schedule创建的同时启动,即:Schedulersche=newStdSchedulerFactory().getScheduler();StdSchedulerFactory的getScheduler()方法调用初始化方法instantiate():sched=instantiate();初始化方法instantiate()的主要作用是完成任务调度器Scheduler所有相关属性的初始化。代码很长。在方法的最后创建一个QuartzScheduler对象:qs=newQuartzScheduler(rsrcs,idleWaitTime,dbFailureRetry);qsInited=真;QuartzScheduler的构造函数会创建QuartzSchedulerThread,创建后交给ThreadExecutor立即启动:如果(resources.getJobStore()instanceofJobListener){addInternal(JobListener){resources.getJobStore());}this.schedThread=newQuartzSchedulerThread(this,resources);ThreadExecutorschedThreadExecutor=resources.getThreadExecutor();schedThreadExecutor.execute(this.schedThread);如果(idleWaitTime>0){this.schedThread.setIdleWaitTime(idleWaitTime);}//省略代码调用后,启动QuartzSchedulerThread线程。调度线程启动后,开始循环等待触发器触发任务。这部分代码逻辑我们后面会详细分析。今天的主角是SimpleThreadPool,他还没有出场,所以想把他拉出来。上例创建Schedule的代码:Schedulersche=newStdSchedulerFactory().getScheduler();将调用StdSchedulerFactory的instantiate()方法。这个方法很长很长。。。暂时不研究,只关注ThreadPool相关的部分。就是在这个冗长的方法中会创建ThreadPool,默认是SimpleThreadPool,然后根据配置完成ThreadPool的初始化,准备好的SimpleThreadPool会被发送到QuartzSchedulerResources中持有。tp.initialize();然后在任务调度线程QuartzSchedulerThread的执行体中(也就是在它的run方法中),如果触发了任务,系统会检查线程池中是否有可用线程:intavailThreadCount=qsRsrcs.getThreadPool().blockForAvailableThreads();如果有可用线程,系统继续各种检查得到要执行的任务,然后把任务交给线程池,线程池负责获取可用线程并执行当前任务:if(qsRsrcs.getThreadPool().runInThread(shell)==false)那么现在我们大概知道了Quartz任务调度的简单逻辑。没有仔细研究任务触发的具体过程,因为我们今天的主要任务是SimpleThreadPool。幸运的是,我们现在知道Quartz在哪里创建线程池以及任务触发时如何将任务交给线程池。我们从这两个方面入手。认识SimpleThreadPoolQuartz的SimpleThreadPool是ThreadPool接口的实现。顾名思义,它是一个简单的线程池实现。几个重要的概念包括:count:只维护一个线程数,没有最大线程数、最大活跃线程数等概念。workers:工作线程,初始化时会创建count个线程,放在worker中。availWorkers:可用线程,worker中没有调度的线程,放在availWorkers中。busyWorkers:当应用程序需要一个线程来执行任务时,从availableWorkers中获取一个可用的线程(从availWorkers中移除)并放入busyWorkers中。SimpleThreadPool的初始化是通过initialize()方法完成的:调用createWorkerThreads创建count个WorkerThread线程对象,并放入workers中。将新创建的Thread一个一个启动,同时放到availWorkers中。可以通过Quartz.properties文件设置计数。假设我们设置的线程数为10,初始化完成后,线程池中会启动10个线程,并且全部可用(都在availWorkers中)。SimpleThreadPool执行任务我们从上面的源码中已经知道,SimpleThreadPool是通过runInThread方法来执行任务的。如果availWorkers中没有可用的线程,则暂停等待,直到其他任务释放线程。while((availWorkers.size()<1)&&!isShutdown){try{nextRunnableLock.wait(500);}catch(InterruptedExceptionignore){}}然后从availWorkers中获取一个线程并将获取到的线程放入busyWorkers中,并使用这个线程执行任务。SimpleThradPool任务执行线程池availWorkers和busyWorkers实际存放的是WorkerThread对象,所以线程池执行任务调用其实就是WorkerThread的run方法。WorkerThread持有一个Runnable成员对象,其实就是我们需要交给线程池执行的任务。run方法不断的检查是否有任务交上来,没有的话就挂断等待。如果发现有任务交上来,则调用Runnable的run方法。这个时候应用层Job对象的execute方法其实是可以调用的,但是后面我们会分析怎么调用。任务执行完毕后,清除WorkerThread的Runnable对象,然后将线程归还给线程池的availableWorkers,从busyWorkers中移除。好的!PreviousJAVA定时任务-JDKTimerNextQuartz-Job&JobDetail