最近因工作需求,研究了一下spring task定時任務,和線程池,有了一定收穫,記錄一下
涉及如下內容
1、如何實現spring task定時任務的配置
2、task裏面的一個job方法如何使用多線程,配置線程池
如何配置等待子線程結束後,再結束主線程
1、如何實現spring task定時任務的配置
因工作需要,需要定時執行一個方法,通過相關比較後,發現spring自帶的task 可以滿足,配置簡單
步驟
1)增加配置文件 ,在applicationContext-cfg.xml 主配置文件裏面添加 相關task標籤
- <beans xmlns="http://www.springframework.org/schema/beans" xmlns:task="http://www.springframework.org/schema/task" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jee="http://www.springframework.org/schema/jee"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://www.springframework.org/schema/tx
- http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context-3.0.xsd
- http://www.springframework.org/schema/task
- http://www.springframework.org/schema/task/spring-task-3.0.xsd
- http://www.springframework.org/schema/jee
- http://www.springframework.org/schema/jee/spring-jee-3.0.xsd">
2)編寫bean類和執行方法
編寫jobService類,裏面實現testjobThread方法,調用的spring注入過的action、service方法
- @Component("jobService")
- public class jobService
- {
- private static Logger logger = Logger.getLogger(jobService.class);
- @Autowired
- private ThreadPoolTaskExecutor taskExecutor;
- final CountDownLatch countDownLatch = new CountDownLatch(3);
- /**
- * @Title: DZFP_job
- * @Description:開票定時任務
- */
- public void testjobThread()
- {
- Date startdate = new Date();
- logger.info("DZFP_job_JOB 開始執行任務...,時間 " + startdate);
- try
- {
- DzfpAction.Dzfp_SendAll();
- }
- catch (Exception e)
- {
- // TODO Auto-generated catch block
- e.printStackTrace();
- logger.error(StringUtil.grabExceptionMessage(e));
- }
- Date enddate = new Date();
- logger.info("DZFP_job_JOB 任務完成...時間 " + enddate + " 耗時 " + String.valueOf(enddate.getTime() - startdate.getTime()) + "毫秒");
- }
3)配置task相關配置文件,在文件applicationContext-cfg.xml 中增加下列內容
pool-size="5" 該參數主要解決,多個調度並行的問題,如下圖5個task任務,建議設置3--5個調度
如果配置參數爲 1,下面5個task任務會依次執行,如果一個時間超出,後面的任務一直在等待,影響業務
- <!-- 定時任務 -->
- <task:scheduler id="scheduler" pool-size="5" />
- <task:scheduled-tasks scheduler="scheduler">
- <!-- 每天7點到7點55, 每隔5分鐘執行一次 "0 0/5 7 * * ?"-->
- <task:scheduled ref="jobService" method="DZFPgetInvoie_job" cron="0 0/30 * * * ?" />
- <task:scheduled ref="jobService" method="DZFPgetInvoie_hong_job" cron="0 0/30 * * * ?" />
- <span style="white-space:pre;"> </span><task:scheduled ref="jobService" method="testjobThread" cron="0/5 * * * * ?" />
- <task:scheduled ref="jobService" method="hzgd_job" cron="0/30 * * * * ?" />
- <task:scheduled ref="jobService" method="alipay_pay_job" cron="0/30 * * * * ?" />
- </task:scheduled-tasks>
使用以上配置後,啓動項目就可以定時執行testjobThread方法裏面的業務了。
2、task裏面的一個job方法如何使用多線程,配置線程池
經過測試,spring task裏面的方法是被串行執行的,比如上面配置的方法 testjobThread方法,5秒執行一次,如果有一個執行過程時間過長,後面的一次調度一直等上次執行結束後,纔會啓動下一次調用。
也就是說spring task是會監控 執行方法的主線程,如果主線程未結束的話,下一次就不會執行。
根據業務需求,這個testjobThread裏面的 業務,需要多線程執行 (批量抽取數據)
spring框架裏面,推薦使用線程池
1)配置線程池 在applicationContext-cfg.xml文件中增加配置如下
- <!-- spring線程池-->
- <bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
- <!-- 線程池維護線程的最少數量 -->
- <property name="corePoolSize" value="5" />
- <!-- 線程池維護線程所允許的空閒時間,默認爲60s -->
- <property name="keepAliveSeconds" value="200" />
- <!-- 線程池維護線程的最大數量 -->
- <property name="maxPoolSize" value="20" />
- <!-- 緩存隊列最大長度 -->
- <property name="queueCapacity" value="20" />
- <!-- 對拒絕task的處理策略 線程池對拒絕任務(無線程可用)的處理策略,目前只支持AbortPolicy、CallerRunsPolicy;默認爲後者-->
- <property name="rejectedExecutionHandler">
- <!-- AbortPolicy:直接拋出java.util.concurrent.RejectedExecutionException異常 -->
- <!-- CallerRunsPolicy:主線程直接執行該任務,執行完之後嘗試添加下一個任務到線程池中,可以有效降低向線程池內添加任務的速度 -->
- <!-- DiscardOldestPolicy:拋棄舊的任務、暫不支持;會導致被丟棄的任務無法再次被執行 -->
- <!-- DiscardPolicy:拋棄當前任務、暫不支持;會導致被丟棄的任務無法再次被執行 -->
- <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
- </property>
- <property name="waitForTasksToCompleteOnShutdown" value="true" />
- </bean>
2)修改業務操作類爲thread類,實現run()方法
添加計數器CountDownLatch ,控制子線程結束後,再結束主線程
注意對象實現@Scope("prototype"),用到了成員變量參數
- package cn.hao24.action;
- import java.util.Date;
- import java.util.concurrent.CountDownLatch;
- import org.springframework.context.annotation.Scope;
- import org.springframework.stereotype.Component;
- import cn.hao24.util.DateUtil;
- import cn.hao24.util.SpringContextUtils;
- @Component("testThreadAction")
- @Scope("prototype")
- public class testThreadAction extends Thread
- {
- /**
- * spring tash默認是單線程 串行執行,即一個方法執行完成前,後面的job不會執行的
- * 但是如果主方法裏面產生了thread線程, 主線程如果不等子線程結束後 就結束的話, task任務會產生多次調度
- */
- private String Treadname;
- private CountDownLatch latch;
- public testThreadAction(String Treadname,CountDownLatch latch){
- this.Treadname=Treadname;
- this.latch=latch;
- }
- @Override
- public void run()
- {
- try
- {
- //主業務方法
- for (int i = 0; i < 10; i++)
- {
- Thread current = Thread.currentThread();
- System.out.println("線程號:"+current.getId() +"--"+current.getName()+" --"+Treadname +":---runing--- "+i+"--"+DateUtil.format(new Date(), "yyyyMMddHHmmss") );
- Thread.sleep(20000);
- }
- }
- catch (InterruptedException e)
- {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }finally{
- //設置實例 執行完畢
- latch.countDown();
- }
- }
- public void setTreadname(String treadname)
- {
- Treadname = treadname;
- }
- public void setLatch(CountDownLatch latch)
- {
- this.latch = latch;
- }
- }
- package cn.hao24.job;
- import java.util.Date;
- import java.util.concurrent.CountDownLatch;
- import javax.annotation.Resource;
- import org.apache.log4j.Logger;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
- import org.springframework.stereotype.Component;
- import cn.hao24.action.DzfpAction;
- import cn.hao24.action.HzgdAction;
- import cn.hao24.action.KJGOrderjob;
- import cn.hao24.action.testThreadAction;
- import cn.hao24.service.ZFBService;
- import cn.hao24.util.SpringContextUtils;
- import cn.hao24.util.StringUtil;
- @Component("jobService")
- public class jobService
- {
- private static Logger logger = Logger.getLogger(jobService.class);
- @Autowired
- private ThreadPoolTaskExecutor taskExecutor;
- final CountDownLatch countDownLatch = new CountDownLatch(3);
- public void testjobThread()
- {
- try
- {
- CountDownLatch latch=new CountDownLatch(3); //java工具類,類似與計數器,主要實現子線程未結束錢,主線程一直等待
- testThreadAction test1 = (testThreadAction)SpringContextUtils.getBean("testThreadAction","test1",latch);
- testThreadAction test2 = (testThreadAction)SpringContextUtils.getBean("testThreadAction","test2",latch);
- testThreadAction test3 = (testThreadAction)SpringContextUtils.getBean("testThreadAction","test3",latch);
- taskExecutor.execute(test1);
- taskExecutor.execute(test2);
- taskExecutor.execute(test3);
- latch.await(); //子線程未結束前,一直等待
- //test1.run();
- }
- catch (Exception e)
- {
- e.printStackTrace();
- logger.error(StringUtil.grabExceptionMessage(e));
- }
- }
- }
執行效果如下:
雖然 testjobThread 5秒執行一次,但是因爲使用到了 latch.await() latch.countDown();需要等子線程執行完畢,纔會進行下一次job
子線程每次循環,會sleep 20秒,從下面結果看,3個線程 每隔20秒纔打印一次。符合最終要求
線程號:29--taskExecutor-3 --test3:---runing--- 0--20170622145500 線程號:28--taskExecutor-2 --test2:---runing--- 0--20170622145500 線程號:27--taskExecutor-1 --test1:---runing--- 0--20170622145500 線程號:28--taskExecutor-2 --test2:---runing--- 1--20170622145520 線程號:27--taskExecutor-1 --test1:---runing--- 1--20170622145520 線程號:29--taskExecutor-3 --test3:---runing--- 1--20170622145520 線程號:29--taskExecutor-3 --test3:---runing--- 2--20170622145540 線程號:28--taskExecutor-2 --test2:---runing--- 2--20170622145540 線程號:27--taskExecutor-1 --test1:---runing--- 2--20170622145540