4.在業務邏輯類TaskServiceImpl中,使用scheduler執行任務。這裏可以動態對任務執行增加、刪除、重啓等等操作。還有一個重要的特性是可以動態的改變Cron表達式,對任務的定時規則進行更改。
/**
* 新增任務。
* @param jobName
* @param jobGroup
* @param cron
*/
public void addJob(String jobName, String jobGroup, String cron, String kindId) {
try {
if (StringUtil.isEmpty(jobName) || StringUtil.isEmpty(jobGroup) || StringUtil.isEmpty(cron)) {
throw new BusinessException("定時任務創建失敗,參數爲空");
}
JobDetail jobDetail = JobBuilder.newJob(QuartzJob.class).withIdentity(jobName, jobGroup).build();
jobDetail.getJobDataMap().put("JobKind", kindId);
//表達式調度構建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
//按新的cronExpression表達式構建一個新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup).withSchedule(scheduleBuilder).build();
scheduler.scheduleJob(jobDetail, trigger);
log.info("新增定時任務,name:" + jobName + ",group:" + jobGroup + ",cron:" + cron);
} catch (SchedulerException e) {
// 對異常的處理
}
}
注意18行的scheduler是由Spring注入的。
這種用法在使用過程中沒有問題,但在web應用在Tomcat6中熱部署時出險了以下嚴重警告,如果忽略這種警告,在頻繁的reload後會造成Tomcat 的 Permgen space OOM。
Jun 24, 2014 5:14:38 AM org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: The web application [/feeder##1.5.0] appears to have started a thread named [scheduler_Worker-1] but has failed to stop it. This is very likely to create a memory leak.
Jun 24, 2014 5:14:38 AM org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: The web application [/feeder##1.5.0] appears to have started a thread named [scheduler_Worker-2] but has failed to stop it. This is very likely to create a memory leak.
Jun 24, 2014 5:14:38 AM org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: The web application [/feeder##1.5.0] appears to have started a thread named [scheduler_Worker-3] but has failed to stop it. This is very likely to create a memory leak.
Jun 24, 2014 5:14:38 AM org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: The web application [/feeder##1.5.0] appears to have started a thread named [scheduler_Worker-4] but has failed to stop it. This is very likely to create a memory leak.
Jun 24, 2014 5:14:38 AM org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: The web application [/feeder##1.5.0] appears to have started a thread named [scheduler_Worker-5] but has failed to stop it. This is very likely to create a memory leak.
Jun 24, 2014 5:14:38 AM org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: The web application [/feeder##1.5.0] appears to have started a thread named [scheduler_Worker-6] but has failed to stop it. This is very likely to create a memory leak.
Jun 24, 2014 5:14:38 AM org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
問題的原因
究其原因是開啓的scheduler_Worker線程沒有關閉。但Spring的SchedulerFactoryBean實現了DisposableBean接口,表示web容器關閉時會執行destroy(),執行內容如下:
/**
* Shut down the Quartz scheduler on bean factory shutdown,
* stopping all scheduled jobs.
*/
public void destroy() throws SchedulerException {
logger.info("Shutting down Quartz Scheduler");
this.scheduler.shutdown(this.waitForJobsToCompleteOnShutdown);
}
表示工廠已經做了shutdown。所以,問題出現在真正執行的scheduler.shutdown(true)。
原來這是Quartz的bug(見https://jira.terracotta.org/jira/browse/QTZ-192),在調用scheduler.shutdown(true)後,Quartz檢查線程並沒有等待那些worker線程被停止就結束了。
網上說在Quartz2.1版本已經修補了這個bug,但筆者使用的2.2.1版本仍舊有問題。
問題的解決
那麼問題如何解決,這裏有一個不太美觀的方案:在調用scheduler.shutdown(true)後,增加一點睡眠時間,等待worker線程全部停止。如下:
1。自定義schedulerFactory繼承Spring的SchedulerFactoryBean,覆蓋destroy()增加延時。
public class SchedulerFactoryBeanWithShutdownDelay extends SchedulerFactoryBean {
/**
* 關於Quartz內存泄漏的不太美觀的解決方案:
* 在調用scheduler.shutdown(true)後增加延時,等待worker線程結束。
*/
@Override
public void destroy() throws SchedulerException {
super.destroy();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
2。把spring配置文件中的SchedulerFactoryBean替換爲自定義的工廠即可。
<bean id="schedulerFactory" class="com.sinosoft.ms.task.SchedulerFactoryBeanWithShutdownDelay">