Java中如何实现每天定时对数据库的操作
現(xiàn)在有一個(gè)很棘手的問(wèn)題:客戶(hù)要贈(zèng)加一個(gè)功能,就是每天晚上11點(diǎn)要統(tǒng)計(jì)一下數(shù)據(jù),并存到一個(gè)文件中,我試著用線程,但是總達(dá)不到理想的效果。請(qǐng)給點(diǎn)思路,多謝了。
我們的開(kāi)發(fā)環(huán)境是tomcat和servlet,我是這樣處理的,在啟動(dòng)tomcat時(shí)就開(kāi)一個(gè)線程來(lái)檢測(cè)時(shí)間并判斷睡眠多長(zhǎng)時(shí)間, 還有沒(méi)有其他的方式?真的沒(méi)思路了。請(qǐng)各位指點(diǎn)指點(diǎn)吧!
==================================
如何在Web工程中實(shí)現(xiàn)任務(wù)計(jì)劃調(diào)度,好多朋友用過(guò)Windows的任務(wù)計(jì)劃
經(jīng)過(guò)查閱較多相關(guān)資料,發(fā)現(xiàn)Java定時(shí)器(java.util.Timer)有定時(shí)觸發(fā)計(jì)劃任務(wù)的功能,通過(guò)配置定時(shí)器的間隔時(shí)間,在某一間隔時(shí)間段之后會(huì)自動(dòng)有規(guī)律的調(diào)用預(yù)先所安排的計(jì)劃任務(wù)(java.util.TimerTask)。另外,由于我們希望當(dāng)Web工程啟動(dòng)時(shí),定時(shí)器能自動(dòng)開(kāi)始計(jì)時(shí),在整個(gè)Web工程的生命期里,定時(shí)器能在每晚深夜觸發(fā)一次報(bào)表計(jì)算引擎。因此定時(shí)器的存放位置也值得考查,不能簡(jiǎn)單的存在于單個(gè)Servlet或JavaBean中,必須能讓定時(shí)器宿主的存活期為整個(gè)Web工程生命期,在工程啟動(dòng)時(shí)能自動(dòng)加載運(yùn)行。結(jié)合這兩點(diǎn),跟Servlet上下文有關(guān)的偵聽(tīng)器就最合適不過(guò)了,通過(guò)在工程的配置文件中加以合理配置,會(huì)在工程啟動(dòng)時(shí)自動(dòng)運(yùn)行,并在整個(gè)工程生命期中處于監(jiān)聽(tīng)狀態(tài)。
下面就Servlet偵聽(tīng)器結(jié)合Java定時(shí)器來(lái)講述整個(gè)實(shí)現(xiàn)過(guò)程。要運(yùn)用Servlet偵聽(tīng)器需要實(shí)現(xiàn)javax.servlet.ServletContextListener接口,同時(shí)實(shí)現(xiàn)它的contextInitialized(ServletContextEvent event)和contextDestroyed(ServletContextEvent event)兩個(gè)接口函數(shù)。考慮定時(shí)器有個(gè)建立和銷(xiāo)毀的過(guò)程,看了前面兩個(gè)接口函數(shù),就不容置疑的把建立的過(guò)程置入contextInitialized,把銷(xiāo)毀的過(guò)程置入contextDestroyed了。
我把ServletContextListener的實(shí)現(xiàn)類(lèi)取名為ContextListener,在其內(nèi)添加一個(gè)定時(shí)器,示例代碼如下所示(為考慮篇幅,僅提供部分代碼供讀者參考):
1. private java.util.Timer timer = null;
2. public void contextInitialized(ServletContextEvent event) {
3. timer = new java.util.Timer(true);
4. event.getServletContext().log("定時(shí)器已啟動(dòng)");
5. timer.schedule(new MyTask(event.getServletContext()), 0, 60*60*1000);
6. event.getServletContext().log("已經(jīng)添加任務(wù)調(diào)度表");
7. }
8. public void contextDestroyed(ServletContextEvent event) {
9. timer.cancel();
10. event.getServletContext().log("定時(shí)器銷(xiāo)毀");
11. }
以上代碼中, timer.schedule(new MyTask(event.getServletContext()), 0, 60*60*1000)這一行為定時(shí)器調(diào)度語(yǔ)句,其中MyTask是自定義需要被調(diào)度的執(zhí)行任務(wù)(在我的財(cái)政數(shù)據(jù)中心項(xiàng)目中就是報(bào)表計(jì)算引擎入口),從java.util.TimerTask繼承,下面會(huì)重點(diǎn)講述,第三個(gè)參數(shù)表示每小時(shí)(即60*60*1000毫秒)被觸發(fā)一次,中間參數(shù)0表示無(wú)延遲。其它代碼相當(dāng)簡(jiǎn)單,不再詳細(xì)說(shuō)明。
下面介紹MyTask的實(shí)現(xiàn),上面的代碼中看到了在構(gòu)造MyTask時(shí),傳入了javax.servlet.ServletContext類(lèi)型參數(shù),是為記錄Servlet日志方便而傳入,因此需要重載MyTask的構(gòu)造函數(shù)(其父類(lèi)java.util.TimerTask原構(gòu)造函數(shù)是沒(méi)有參數(shù)的)。在timer.schedule()的調(diào)度中,設(shè)置了每小時(shí)調(diào)度一次,因此如果想實(shí)現(xiàn)調(diào)度任務(wù)每24小時(shí)被執(zhí)行一次,還需要判斷一下時(shí)鐘點(diǎn),以常量C_SCHEDULE_HOUR表示(晚上12點(diǎn),也即0點(diǎn))。同時(shí)為防止24小時(shí)執(zhí)行下來(lái),任務(wù)還未執(zhí)行完(當(dāng)然,一般任務(wù)是沒(méi)有這么長(zhǎng)的),避免第二次又被調(diào)度以引起執(zhí)行沖突,設(shè)置了當(dāng)前是否正在執(zhí)行的狀態(tài)標(biāo)志isRunning。示例代碼如下所示:
1. private static final int C_SCHEDULE_HOUR = 0;
2. private static boolean isRunning = false;
3. private ServletContext context = null;
4. public MyTask(ServletContext context) {
5. this.context = context;
6. }
7. public void run() {
8. Calendar cal = Calendar.getInstance();
9. if (!isRunning) {
10. if (C_SCHEDULE_HOUR == cal.get(Calendar.HOUR_OF_DAY)) {
11. isRunning = true;
12. context.log("開(kāi)始執(zhí)行指定任務(wù)");
13.
14. //TODO 添加自定義的詳細(xì)任務(wù),以下只是示例
15. int i = 0;
16. while (i++ < 10) {
17. context.log("已完成任務(wù)的" + i + "/" + 10);
18. }
19.
20. isRunning = false;
21. context.log("指定任務(wù)執(zhí)行結(jié)束");
22. }
23. } else {
24. context.log("上一次任務(wù)執(zhí)行還未結(jié)束");
25. }
26. }
上面代碼中“//TODO……”之下四行是真正被調(diào)度執(zhí)行的演示代碼(在我的財(cái)政數(shù)據(jù)中心項(xiàng)目中就是報(bào)表計(jì)算過(guò)程),您可以換成自己希望執(zhí)行的語(yǔ)句。
到這兒,ServletContextListener和MyTask的代碼都已完整了。最后一步就是把ServletContextListener部署到您的Web工程中去,在您工程的web.xml配置文件中加入如下三行:
<listener>
<listener-class>com.test.ContextListener</listener-class>
</listener>
當(dāng)然,上面的com.test得換成您自己的包名了。保存web.xml文件后,把工程打包部署到Tomcat中即可。任務(wù)會(huì)在每晚12點(diǎn)至凌晨1點(diǎn)之間被執(zhí)行,上面的代碼會(huì)在Tomcat的日志文件中記錄如下:
2003-12-05 0:21:39 開(kāi)始執(zhí)行指定任務(wù)
2003-12-05 0:21:39 已完成任務(wù)的1/10
……
2003-12-05 0:21:39 已完成任務(wù)的10/10
2003-12-05 0:21:39 指定任務(wù)執(zhí)行結(jié)束
Feedback
# re: Q : 如何實(shí)現(xiàn)每天定時(shí)對(duì)數(shù)據(jù)庫(kù)的操作 回復(fù) 更多評(píng)論
2006-01-22 22:37 by caid'weblog
http://www2.uuzone.com/blog/seril/73267.htm
使用Timmer使Struts修改struts-config.xml文件不用重新啟動(dòng)服務(wù)器 1
在做struts應(yīng)用的時(shí)候,經(jīng)常學(xué)要修改struts-config.xml文件,在每次修改完之后只有重新啟動(dòng)服務(wù)器才能讓修改生效。因此做了一個(gè)Listener,在應(yīng)用啟動(dòng)的時(shí)候開(kāi)始,每隔一段時(shí)間就去檢查一下struts-config.xml文件的最后修改時(shí)間,如果修改時(shí)間變化了,就重新讀取struts-config.xml,將對(duì)應(yīng)的配置放到ServletContext中去。
一.LoadResourceListener
import java.util.Timer;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import kick.utils.Constants;
public class LoadResourceListener implements ServletContextListener {
/* (non-Javadoc)
* @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
*/
public void contextInitialized(ServletContextEvent event) {
Timer loadResource = new Timer();
//獲取ServletContext
ServletContext servletContext = event.getServletContext();
//創(chuàng)建一個(gè)LoadResourceTimerTask 的實(shí)例
LoadResourceTimerTask loadResourceTimerTask = new LoadResourceTimerTask(servletContext);
//將剛創(chuàng)建的TimerTask的實(shí)例的運(yùn)行計(jì)劃訂為:馬上開(kāi)始,每隔20×1000ms運(yùn)行一次
loadResource.schedule(loadResourceTimerTask,0,Constants.DELAY_UPDATE_TIME);
}
/* (non-Javadoc)
* @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.ServletContextEvent)
*/
public void contextDestroyed(ServletContextEvent arg0) {
}
}
二.配置LoadResourceListener
在filter的配置下面添加上如下的配置
<listener>
<listener-class>kick.load.resource.LoadResourceListener</listener-class>
</listener>
三.LoadResourceTimerTask類(lèi)
/*
* Created on 2005-9-6
*
* TODO To change the template for this generated file go to
* Window - Preferences - Java - Code Style - Code Templates
*/
package kick.load.resource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.MissingResourceException;
import java.util.Set;
import java.util.TimerTask;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.UnavailableException;
import javax.sql.DataSource;
import kick.utils.Constants;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.collections.FastHashMap;
import org.apache.commons.digester.Digester;
import org.apache.commons.digester.RuleSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.Globals;
import org.apache.struts.config.ConfigRuleSet;
import org.apache.struts.config.DataSourceConfig;
import org.apache.struts.config.FormBeanConfig;
import org.apache.struts.config.MessageResourcesConfig;
import org.apache.struts.config.ModuleConfig;
import org.apache.struts.config.ModuleConfigFactory;
import org.apache.struts.util.MessageResources;
import org.apache.struts.util.MessageResourcesFactory;
import org.apache.struts.util.RequestUtils;
import org.apache.struts.util.ServletContextWriter;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
public class LoadResourceTimerTask extends TimerTask {
private ServletContext context = null;
/**
* <p>
* The resources object for our internal resources.
* </p>
*/
protected MessageResources internal = null;
/**
* <p>
* The Digester used to produce ModuleConfig objects from a Struts
* configuration file.
* </p>
*
* @since Struts 1.1
*/
protected Digester configDigester = null;
/**
* <p>
* The Java base name of our internal resources.
* </p>
*
* @since Struts 1.1
*/
protected String internalName = "org.apache.struts.action.ActionResources";
/**
* <p>
* Commons Logging instance.
* </p>
*
* @since Struts 1.1
*/
protected static Log log = LogFactory.getLog(LoadResourceTimerTask.class);
private List initParams = null;
/**
* <p>
* The set of public identifiers, and corresponding resource names, for the
* versions of the configuration file DTDs that we know about. There
* <strong>MUST </strong> be an even number of Strings in this list!
* </p>
*/
protected String registrations[] = { "-//Apache Software Foundation//DTD Struts Configuration 1.0//EN",
"/org/apache/struts/resources/struts-config_1_0.dtd",
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN",
"/org/apache/struts/resources/struts-config_1_1.dtd",
"-//Apache Software Foundation//DTD Struts Configuration 1.2//EN",
"/org/apache/struts/resources/struts-config_1_2.dtd",
"-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN", "/org/apache/struts/resources/web-app_2_2.dtd",
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN", "/org/apache/struts/resources/web-app_2_3.dtd" };
/**
* <p>
* The JDBC data sources that has been configured for this module, if any,
* keyed by the servlet context attribute under which they are stored.
* </p>
*/
protected FastHashMap dataSources = new FastHashMap();
/**
* <p>
* Comma-separated list of context-relative path(s) to our configuration
* resource(s) for the default module.
* </p>
*/
protected String config = "/WEB-INF/struts-config.xml";
private List resourcesName = new ArrayList();
private Set resourceFiles = new HashSet();
public LoadResourceTimerTask(ServletContext context) {
this.context = context;
try {
initInternal();
parseWeb();
parseConfigFile();
parseResource();
// parseConfigFile();
} catch (ServletException e) {
System.out.println(e.getMessage());
e.printStackTrace();
throw new RuntimeException(e);
}
}
/*
* (non-Javadoc)
*
* @see java.util.TimerTask#run()
*/
public void run() {
try {
reLoadConfigFile();
reLoadResource();
} catch (Exception e) {
}
}
/**
*
*/
private void parseConfigFile() {
InitParam initParam = null;
for (int i = 0; i < initParams.size(); i++) {
initParam = (InitParam) initParams.get(i);
String name = initParam.getName();
if (!name.startsWith("config")) {
continue;
}
String prefix = name.substring(6);
String paths = initParam.getValue();
// Process each specified resource path
while (paths.length() > 0) {
// digester.push(config);
String path = null;
int comma = paths.indexOf(',');
if (comma >= 0) {
path = paths.substring(0, comma).trim();
paths = paths.substring(comma + 1);
} else {
path = paths.trim();
paths = "";
}
if (path.length() < 1) {
break;
}
File file = new File(getServletContext().getRealPath(path));
StrutsConfig s = new StrutsConfig();
Digester d = new Digester();
d.push(s);
d.setNamespaceAware(false);
d.setValidating(false);
for (int j = 0; j < registrations.length; j += 2) {
URL url = this.getClass().getResource(registrations[j + 1]);
if (url != null) {
d.register(registrations[j], url.toString());
}
}
d.addObjectCreate("struts-config/message-resources", Resource.class);
d.addSetProperties("struts-config/message-resources", "parameter", "parameter");
d.addSetNext("struts-config/message-resources", "addResource");
// d.addCallMethod("web-struts-config/message-resources",
// "setParameter", 0);
try {
d.parse(file);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SAXException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// String resourcePath =
// ((Resource)s.getResources()).toFilePaht();
List rs = s.getResources();
for (int ii = 0; ii < rs.size(); ii++) {
resourcesName.add(((Resource) rs.get(ii)).toFilePaht());
}
}
}
}
public void parseResource() {
int index = 0;
String dirName = "";
String resource = "";
String subFileName = "";
for (int i = 0; i < resourcesName.size(); i++) {
resource = (String) resourcesName.get(i);
index = resource.lastIndexOf("/");
dirName = resource.substring(0, index);
subFileName = resource.substring(index + 1, resource.length());
File file = new File(getServletContext().getRealPath("/WEB-INF/classes/" + dirName));
if (file.isDirectory()) {
String[] fileNames = file.list();
if (fileNames != null) {
for (int j = 0; j < fileNames.length; j++) {
if (fileNames[j] != null) {
if (fileNames[j].trim().startsWith(subFileName.trim())) {
resourceFiles.add(dirName + "/" + fileNames[j]);
// System.out.println("The file Name : '" +
// subFileName + "'");
// System.out.println("Add file name : '" +
// fileNames[j] + "'");
}
}
}
}
}
}
}
private void reLoadConfigFile() {
try {
InitParam initParam = null;
for (int i = 0; i < initParams.size(); i++) {
initParam = (InitParam) initParams.get(i);
String name = initParam.getName();
if (!name.startsWith("config")) {
continue;
}
String prefix = name.substring(6);
String paths = initParam.getValue();
// Process each specified resource path
while (paths.length() > 0) {
// digester.push(config);
String path = null;
int comma = paths.indexOf(',');
if (comma >= 0) {
path = paths.substring(0, comma).trim();
paths = paths.substring(comma + 1);
} else {
path = paths.trim();
paths = "";
}
if (path.length() < 1) {
break;
}
File file = new File(getServletContext().getRealPath(path));
if ((System.currentTimeMillis() - file.lastModified()) < Constants.DELAY_UPDATE_TIME) {
log.debug("The struts-config.xml is changed,will be reload into context.");
log.debug("The file name is : '" + path + "'");
log.debug("Refash the resource file config list!");
parseConfigFile();
log.debug("Refash the resource file list!");
parseResource();
ModuleConfig moduleConfig = initModuleConfig(prefix, path);
initModuleMessageResources(moduleConfig);
initModuleDataSources(moduleConfig);
moduleConfig.freeze();
log.debug("Reload the config file success!");
}
this.initModulePrefixes(this.getServletContext());
}
}
} catch (Exception e) {
}
}
private void reLoadConfig(String prefix, String path) {
try {
ModuleConfig moduleConfig = initModuleConfig(prefix, path);
initModuleMessageResources(moduleConfig);
initModuleDataSources(moduleConfig);
moduleConfig.freeze();
} catch (Exception e) {
}
}
private void reLoadResource() {
Iterator it = resourceFiles.iterator();
String fileName = "";
while (it.hasNext()) {
fileName = (String) it.next();
File file = new File(this.getServletContext().getRealPath("/WEB-INF/classes/" + fileName));
if ((System.currentTimeMillis() - file.lastModified()) < Constants.DELAY_UPDATE_TIME) {
log.debug("Update the '" + file.getName() + "' property file!");
updateConfigFile();
}
}
}
/**
*
*/
private void updateConfigFile() {
InitParam initParam = null;
for (int i = 0; i < initParams.size(); i++) {
initParam = (InitParam) initParams.get(i);
String name = initParam.getName();
if (!name.startsWith("config")) {
continue;
}
String prefix = name.substring(6);
String paths = initParam.getValue();
// Process each specified resource path
while (paths.length() > 0) {
// digester.push(config);
String path = null;
int comma = paths.indexOf(',');
if (comma >= 0) {
path = paths.substring(0, comma).trim();
paths = paths.substring(comma + 1);
} else {
path = paths.trim();
paths = "";
}
if (path.length() < 1) {
break;
}
File file = new File(getServletContext().getRealPath(path));
file.setLastModified(System.currentTimeMillis());
}
}
}
# Java語(yǔ)言中Timer類(lèi)的簡(jiǎn)潔用法 回復(fù) 更多評(píng)論
2006-01-22 23:01 by caid'weblog
所有類(lèi)型的 Java 應(yīng)用程序一般都需要計(jì)劃重復(fù)執(zhí)行的任務(wù)。企業(yè)應(yīng)用程序需要計(jì)劃每日的日志或者晚間批處理過(guò)程。一個(gè) J2SE 或者 J2ME 日歷應(yīng)用程序需要根據(jù)用戶(hù)的約定計(jì)劃鬧鈴時(shí)間。不過(guò),標(biāo)準(zhǔn)的調(diào)度類(lèi) Timer 和 TimerTask 沒(méi)有足夠的靈活性,無(wú)法支持通常需要的計(jì)劃任務(wù)類(lèi)型。在本文中,Java 開(kāi)發(fā)人員 Tom White 向您展示了如何構(gòu)建一個(gè)簡(jiǎn)單通用的計(jì)劃框架,以用于執(zhí)行任意復(fù)雜的計(jì)劃任務(wù)。
我將把 java.util.Timer 和 java.util.TimerTask 統(tǒng)稱(chēng)為 Java 計(jì)時(shí)器框架,它們使程序員可以很容易地計(jì)劃簡(jiǎn)單的任務(wù)(注意這些類(lèi)也可用于 J2ME 中)。在 Java 2 SDK, Standard Edition, Version 1.3 中引入這個(gè)框架之前,開(kāi)發(fā)人員必須編寫(xiě)自己的調(diào)度程序,這需要花費(fèi)很大精力來(lái)處理線程和復(fù)雜的 Object.wait() 方法。不過(guò),Java 計(jì)時(shí)器框架沒(méi)有足夠的能力來(lái)滿(mǎn)足許多應(yīng)用程序的計(jì)劃要求。甚至一項(xiàng)需要在每天同一時(shí)間重復(fù)執(zhí)行的任務(wù),也不能直接使用 Timer 來(lái)計(jì)劃,因?yàn)樵谙牧顣r(shí)開(kāi)始和結(jié)束時(shí)會(huì)出現(xiàn)時(shí)間跳躍。
本文展示了一個(gè)通用的 Timer 和 TimerTask 計(jì)劃框架,從而允許更靈活的計(jì)劃任務(wù)。這個(gè)框架非常簡(jiǎn)單 —— 它包括兩個(gè)類(lèi)和一個(gè)接口 —— 并且容易掌握。如果您習(xí)慣于使用 Java 定時(shí)器框架,那么您應(yīng)該可以很快地掌握這個(gè)計(jì)劃框架。
計(jì)劃單次任務(wù)
計(jì)劃框架建立在 Java 定時(shí)器框架類(lèi)的基礎(chǔ)之上。因此,在解釋如何使用計(jì)劃框架以及如何實(shí)現(xiàn)它之前,我們將首先看看如何用這些類(lèi)進(jìn)行計(jì)劃。
想像一個(gè)煮蛋計(jì)時(shí)器,在數(shù)分鐘之后(這時(shí)蛋煮好了)它會(huì)發(fā)出聲音提醒您。清單 1 中的代碼構(gòu)成了一個(gè)簡(jiǎn)單的煮蛋計(jì)時(shí)器的基本結(jié)構(gòu),它用 Java 語(yǔ)言編寫(xiě):
清單 1. EggTimer 類(lèi)
package org.tiling.scheduling.examples;import java.util.Timer;import java.util.TimerTask;public class EggTimer { private final Timer timer = new Timer(); private final int minutes; public EggTimer(int minutes) { this.minutes = minutes; } public void start() { timer.schedule(new TimerTask() { public void run() { playSound(); timer.cancel(); } private void playSound() { System.out.println("Your egg is ready!"); // Start a new thread to play a sound... } }, minutes * 60 * 1000); } public static void main(String[] args) { EggTimer eggTimer = new EggTimer(2); eggTimer.start(); }}
EggTimer 實(shí)例擁有一個(gè) Timer 實(shí)例,用于提供必要的計(jì)劃。用 start() 方法啟動(dòng)煮蛋計(jì)時(shí)器后,它就計(jì)劃了一個(gè) TimerTask,在指定的分鐘數(shù)之后執(zhí)行。時(shí)間到了,Timer 就在后臺(tái)調(diào)用 TimerTask 的 start() 方法,這會(huì)使它發(fā)出聲音。在取消計(jì)時(shí)器后這個(gè)應(yīng)用程序就會(huì)中止。
計(jì)劃重復(fù)執(zhí)行的任務(wù)
通過(guò)指定一個(gè)固定的執(zhí)行頻率或者固定的執(zhí)行時(shí)間間隔,Timer 可以對(duì)重復(fù)執(zhí)行的任務(wù)進(jìn)行計(jì)劃。不過(guò),有許多應(yīng)用程序要求更復(fù)雜的計(jì)劃。例如,每天清晨在同一時(shí)間發(fā)出叫醒鈴聲的鬧鐘不能簡(jiǎn)單地使用固定的計(jì)劃頻率 86400000 毫秒(24 小時(shí)),因?yàn)樵阽姄芸旎蛘邠苈?#xff08;如果您的時(shí)區(qū)使用夏令時(shí))的那些天里,叫醒可能過(guò)晚或者過(guò)早。解決方案是使用日歷算法計(jì)算每日事件下一次計(jì)劃發(fā)生的時(shí)間。 而這正是計(jì)劃框架所支持的。考慮清單 2 中的 AlarmClock 實(shí)現(xiàn):
清單 2. AlarmClock 類(lèi)
package org.tiling.scheduling.examples;import java.text.SimpleDateFormat;import java.util.Date;import org.tiling.scheduling.Scheduler;import org.tiling.scheduling.SchedulerTask;import org.tiling.scheduling.examples.iterators.DailyIterator;public class AlarmClock { private final Scheduler scheduler = new Scheduler(); private final SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMM yyyy HH:mm:ss.SSS"); private final int hourOfDay, minute, second; public AlarmClock(int hourOfDay, int minute, int second) { this.hourOfDay = hourOfDay; this.minute = minute; this.second = second; } public void start() { scheduler.schedule(new SchedulerTask() { public void run() { soundAlarm(); } private void soundAlarm() { System.out.println("Wake up! " + "It"s " + dateFormat.format(new Date())); // Start a new thread to sound an alarm... } }, new DailyIterator(hourOfDay, minute, second)); } public static void main(String[] args) { AlarmClock alarmClock = new AlarmClock(7, 0, 0); alarmClock.start(); }}
注意這段代碼與煮蛋計(jì)時(shí)器應(yīng)用程序非常相似。AlarmClock 實(shí)例擁有一個(gè) Scheduler (而不是 Timer)實(shí)例,用于提供必要的計(jì)劃。啟動(dòng)后,這個(gè)鬧鐘對(duì) SchedulerTask (而不是 TimerTask)進(jìn)行調(diào)度用以發(fā)出報(bào)警聲。這個(gè)鬧鐘不是計(jì)劃一個(gè)任務(wù)在固定的延遲時(shí)間后執(zhí)行,而是用 DailyIterator 類(lèi)描述其計(jì)劃。在這里,它只是計(jì)劃任務(wù)在每天上午 7:00 執(zhí)行。下面是一個(gè)正常運(yùn)行情況下的輸出:
Wake up! It"s 24 Aug 2003 07:00:00.023Wake up! It"s 25 Aug 2003 07:00:00.001Wake up! It"s 26 Aug 2003 07:00:00.058Wake up! It"s 27 Aug 2003 07:00:00.015Wake up! It"s 28 Aug 2003 07:00:00.002...
DailyIterator 實(shí)現(xiàn)了 ScheduleIterator,這是一個(gè)將 SchedulerTask 的計(jì)劃執(zhí)行時(shí)間指定為一系列 java.util.Date 對(duì)象的接口。然后 next() 方法按時(shí)間先后順序迭代 Date 對(duì)象。返回值 null 會(huì)使任務(wù)取消(即它再也不會(huì)運(yùn)行)—— 這樣的話,試圖再次計(jì)劃將會(huì)拋出一個(gè)異常。清單 3 包含 ScheduleIterator 接口:
清單 3. ScheduleIterator 接口
package org.tiling.scheduling;import java.util.Date;public interface ScheduleIterator { public Date next();}
DailyIterator 的 next() 方法返回表示每天同一時(shí)間(上午 7:00)的 Date 對(duì)象,如清單 4 所示。所以,如果對(duì)新構(gòu)建的 next() 類(lèi)調(diào)用 next(),那么將會(huì)得到傳遞給構(gòu)造函數(shù)的那個(gè)日期當(dāng)天或者后面一天的 7:00 AM。再次調(diào)用 next() 會(huì)返回后一天的 7:00 AM,如此重復(fù)。為了實(shí)現(xiàn)這種行為,DailyIterator 使用了 java.util.Calendar 實(shí)例。構(gòu)造函數(shù)會(huì)在日歷中加上一天,對(duì)日歷的這種設(shè)置使得第一次調(diào)用 next() 會(huì)返回正確的 Date。注意代碼沒(méi)有明確地提到夏令時(shí)修正,因?yàn)?Calendar 實(shí)現(xiàn)(在本例中是 GregorianCalendar)負(fù)責(zé)對(duì)此進(jìn)行處理,所以不需要這樣做。
清單 4. DailyIterator 類(lèi)
package org.tiling.scheduling.examples.iterators;import org.tiling.scheduling.ScheduleIterator;import java.util.Calendar;import java.util.Date;/** * A DailyIterator class returns a sequence of dates on subsequent days * representing the same time each day. */public class DailyIterator implements ScheduleIterator { private final int hourOfDay, minute, second; private final Calendar calendar = Calendar.getInstance(); public DailyIterator(int hourOfDay, int minute, int second) { this(hourOfDay, minute, second, new Date()); } public DailyIterator(int hourOfDay, int minute, int second, Date date) { this.hourOfDay = hourOfDay; this.minute = minute; this.second = second; calendar.setTime(date); calendar.set(Calendar.HOUR_OF_DAY, hourOfDay); calendar.set(Calendar.MINUTE, minute); calendar.set(Calendar.SECOND, second); calendar.set(Calendar.MILLISECOND, 0); if (!calendar.getTime().before(date)) { calendar.add(Calendar.DATE, -1); } } public Date next() { calendar.add(Calendar.DATE, 1); return calendar.getTime(); }}
實(shí)現(xiàn)計(jì)劃框架
在上一節(jié),我們學(xué)習(xí)了如何使用計(jì)劃框架,并將它與 Java 定時(shí)器框架進(jìn)行了比較。下面,我將向您展示如何實(shí)現(xiàn)這個(gè)框架。除了 清單 3 中展示的 ScheduleIterator 接口,構(gòu)成這個(gè)框架的還有另外兩個(gè)類(lèi) —— Scheduler 和 SchedulerTask 。這些類(lèi)實(shí)際上在內(nèi)部使用 Timer 和 SchedulerTask,因?yàn)橛?jì)劃其實(shí)就是一系列的單次定時(shí)器。清單 5 和 6 顯示了這兩個(gè)類(lèi)的源代碼:
清單 5. Scheduler
package org.tiling.scheduling;import java.util.Date;import java.util.Timer;import java.util.TimerTask;public class Scheduler { class SchedulerTimerTask extends TimerTask { private SchedulerTask schedulerTask; private ScheduleIterator iterator; public SchedulerTimerTask(SchedulerTask schedulerTask, ScheduleIterator iterator) { this.schedulerTask = schedulerTask; this.iterator = iterator; } public void run() { schedulerTask.run(); reschedule(schedulerTask, iterator); } } private final Timer timer = new Timer(); public Scheduler() { } public void cancel() { timer.cancel(); } public void schedule(SchedulerTask schedulerTask, ScheduleIterator iterator) { Date time = iterator.next(); if (time == null) { schedulerTask.cancel(); } else { synchronized(schedulerTask.lock) { if (schedulerTask.state != SchedulerTask.VIRGIN) { throw new IllegalStateException("Task already scheduled " + "or cancelled"); } schedulerTask.state = SchedulerTask.SCHEDULED; schedulerTask.timerTask = new SchedulerTimerTask(schedulerTask, iterator); timer.schedule(schedulerTask.timerTask, time); } } } private void reschedule(SchedulerTask schedulerTask, ScheduleIterator iterator) { Date time = iterator.next(); if (time == null) { schedulerTask.cancel(); } else { synchronized(schedulerTask.lock) { if (schedulerTask.state != SchedulerTask.CANCELLED) { schedulerTask.timerTask = new SchedulerTimerTask(schedulerTask, iterator); timer.schedule(schedulerTask.timerTask, time); } } } }}
清單 6 顯示了 SchedulerTask 類(lèi)的源代碼:
package org.tiling.scheduling;import java.util.TimerTask;public abstract class SchedulerTask implements Runnable { final Object lock = new Object(); int state = VIRGIN; static final int VIRGIN = 0; static final int SCHEDULED = 1; static final int CANCELLED = 2; TimerTask timerTask; protected SchedulerTask() { } public abstract void run(); public boolean cancel() { synchronized(lock) { if (timerTask != null) { timerTask.cancel(); } boolean result = (state == SCHEDULED); state = CANCELLED; return result; } } public long scheduledExecutionTime() { synchronized(lock) { return timerTask == null ? 0 : timerTask.scheduledExecutionTime(); } }}
就像煮蛋計(jì)時(shí)器,Scheduler 的每一個(gè)實(shí)例都擁有 Timer 的一個(gè)實(shí)例,用于提供底層計(jì)劃。Scheduler 并沒(méi)有像實(shí)現(xiàn)煮蛋計(jì)時(shí)器時(shí)那樣使用一個(gè)單次定時(shí)器,它將一組單次定時(shí)器串接在一起,以便在由 ScheduleIterator 指定的各個(gè)時(shí)間執(zhí)行 SchedulerTask 類(lèi)。
考慮 Scheduler 上的 public schedule() 方法 —— 這是計(jì)劃的入口點(diǎn),因?yàn)樗强蛻?hù)調(diào)用的方法(在 取消任務(wù) 一節(jié)中將描述僅有的另一個(gè) public 方法 cancel())。通過(guò)調(diào)用 ScheduleIterator 接口的 next(),發(fā)現(xiàn)第一次執(zhí)行 SchedulerTask 的時(shí)間。然后通過(guò)調(diào)用底層 Timer 類(lèi)的單次 schedule() 方法,啟動(dòng)計(jì)劃在這一時(shí)刻執(zhí)行。為單次執(zhí)行提供的 TimerTask 對(duì)象是嵌入的 SchedulerTimerTask 類(lèi)的一個(gè)實(shí)例,它包裝了任務(wù)和迭代器(iterator)。在指定的時(shí)間,調(diào)用嵌入類(lèi)的 run() 方法,它使用包裝的任務(wù)和迭代器引用以便重新計(jì)劃任務(wù)的下一次執(zhí)行。reschedule() 方法與 schedule() 方法非常相似,只不過(guò)它是 private 的,并且執(zhí)行一組稍有不同的 SchedulerTask 狀態(tài)檢查。重新計(jì)劃過(guò)程反復(fù)重復(fù),為每次計(jì)劃執(zhí)行構(gòu)造一個(gè)新的嵌入類(lèi)實(shí)例,直到任務(wù)或者調(diào)度程序被取消(或者 JVM 關(guān)閉)。
類(lèi)似于 TimerTask,SchedulerTask 在其生命周期中要經(jīng)歷一系列的狀態(tài)。創(chuàng)建后,它處于 VIRGIN 狀態(tài),這表明它從沒(méi)有計(jì)劃過(guò)。計(jì)劃以后,它就變?yōu)?SCHEDULED 狀態(tài),再用下面描述的方法之一取消任務(wù)后,它就變?yōu)?CANCELLED 狀態(tài)。管理正確的狀態(tài)轉(zhuǎn)變 —— 如保證不對(duì)一個(gè)非 VIRGIN 狀態(tài)的任務(wù)進(jìn)行兩次計(jì)劃 —— 增加了 Scheduler 和 SchedulerTask 類(lèi)的復(fù)雜性。在進(jìn)行可能改變?nèi)蝿?wù)狀態(tài)的操作時(shí),代碼必須同步任務(wù)的鎖對(duì)象。
取消任務(wù)
取消計(jì)劃任務(wù)有三種方式。第一種是調(diào)用 SchedulerTask 的 cancel() 方法。這很像調(diào)用 TimerTask 的 cancel()方法:任務(wù)再也不會(huì)運(yùn)行了,不過(guò)已經(jīng)運(yùn)行的任務(wù)仍會(huì)運(yùn)行完成。 cancel() 方法的返回值是一個(gè)布爾值,表示如果沒(méi)有調(diào)用 cancel() 的話,計(jì)劃的任務(wù)是否還會(huì)運(yùn)行。更準(zhǔn)確地說(shuō),如果任務(wù)在調(diào)用 cancel() 之前是 SCHEDULED 狀態(tài),那么它就返回 true。如果試圖再次計(jì)劃一個(gè)取消的(甚至是已計(jì)劃的)任務(wù),那么 Scheduler 就會(huì)拋出一個(gè) IllegalStateException。
取消計(jì)劃任務(wù)的第二種方式是讓 ScheduleIterator 返回 null。這只是第一種方式的簡(jiǎn)化操作,因?yàn)?Scheduler 類(lèi)調(diào)用 SchedulerTask 類(lèi)的 cancel()方法。如果您想用迭代器而不是任務(wù)來(lái)控制計(jì)劃停止時(shí)間時(shí),就用得上這種取消任務(wù)的方式了。
第三種方式是通過(guò)調(diào)用其 cancel() 方法取消整個(gè) Scheduler。這會(huì)取消調(diào)試程序的所有任務(wù),并使它不能再計(jì)劃任何任務(wù)。
擴(kuò)展 cron 實(shí)用程序
可以將計(jì)劃框架比作 UNIX 的 cron 實(shí)用程序,只不過(guò)計(jì)劃次數(shù)的規(guī)定是強(qiáng)制性而不是聲明性的。例如,在 AlarmClock 實(shí)現(xiàn)中使用的 DailyIterator 類(lèi),它的計(jì)劃與 cron 作業(yè)的計(jì)劃相同,都是由以 0 7 * * * 開(kāi)始的 crontab 項(xiàng)指定的(這些字段分別指定分鐘、小時(shí)、日、月和星期)。
不過(guò),計(jì)劃框架比 cron 更靈活。想像一個(gè)在早晨打開(kāi)熱水的 HeatingController 應(yīng)用程序。我想指示它“在每個(gè)工作日上午 8:00 打開(kāi)熱水,在周未上午 9:00 打開(kāi)熱水”。使用 cron,我需要兩個(gè) crontab 項(xiàng)(0 8 * * 1,2,3,4,5 和 0 9 * * 6,7)。而使用 ScheduleIterator 的解決方案更簡(jiǎn)潔一些,因?yàn)槲铱梢允褂脧?fù)合(composition)來(lái)定義單一迭代器。清單 7 顯示了其中的一種方法:
清單 7. 用復(fù)合定義單一迭代器
int[] weekdays = new int[] { Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY }; int[] weekend = new int[] { Calendar.SATURDAY, Calendar.SUNDAY }; ScheduleIterator i = new CompositeIterator( new ScheduleIterator[] { new RestrictedDailyIterator(8, 0, 0, weekdays), new RestrictedDailyIterator(9, 0, 0, weekend) } );
RestrictedDailyIterator 類(lèi)很像 DailyIterator,只不過(guò)它限制為只在一周的特定日子里運(yùn)行,而一個(gè) CompositeIterator 類(lèi)取得一組 ScheduleIterators,并將日期正確排列到單個(gè)計(jì)劃中。
有許多計(jì)劃是 cron 無(wú)法生成的,但是 ScheduleIterator 實(shí)現(xiàn)卻可以。例如,“每個(gè)月的最后一天”描述的計(jì)劃可以用標(biāo)準(zhǔn) Java 日歷算法來(lái)實(shí)現(xiàn)(用 Calendar 類(lèi)),而用 cron 則無(wú)法表達(dá)它。應(yīng)用程序甚至無(wú)需使用 Calendar 類(lèi)。在本文的源代碼(請(qǐng)參閱 參考資料)中,我加入了一個(gè)安全燈控制器的例子,它按“在日落之前 15 分鐘開(kāi)燈”這一計(jì)劃運(yùn)行。這個(gè)實(shí)現(xiàn)使用了 Calendrical Calculations Software Package,用于計(jì)算當(dāng)?shù)?#xff08;給定經(jīng)度和緯度)的日落時(shí)間。
實(shí)時(shí)保證
在編寫(xiě)使用計(jì)劃的應(yīng)用程序時(shí),一定要了解框架在時(shí)間方面有什么保證。我的任務(wù)是提前還是延遲執(zhí)行?如果有提前或者延遲,偏差最大值是多少?不幸的是,對(duì)這些問(wèn)題沒(méi)有簡(jiǎn)單的答案。不過(guò)在實(shí)際中,它的行為對(duì)于很多應(yīng)用程序已經(jīng)足夠了。下面的討論假設(shè)系統(tǒng)時(shí)鐘是正確的。
因?yàn)?Scheduler 將計(jì)劃委托給 Timer 類(lèi),Scheduler 可以做出的實(shí)時(shí)保證與 Timer 的一樣。Timer 用 Object.wait(long) 方法計(jì)劃任務(wù)。當(dāng)前線程要等待直到喚醒它,喚醒可能出于以下原因之一:
1.另一個(gè)線程調(diào)用對(duì)象的 notify() 或者 notifyAll() 方法。
2.線程被另一個(gè)線程中斷。
3.在沒(méi)有通知的情況下,線程被喚醒(稱(chēng)為 spurious wakeup,Joshua Bloch 的 Effective Java Programming Language Guide 一書(shū)中 Item 50 對(duì)其進(jìn)行了描述 。
4.規(guī)定的時(shí)間已到。
對(duì)于 Timer 類(lèi)來(lái)說(shuō),第一種可能性是不會(huì)發(fā)生的,因?yàn)閷?duì)其調(diào)用 wait() 的對(duì)象是私有的。即便如此,Timer 實(shí)現(xiàn)仍然針對(duì)前三種提前喚醒的原因進(jìn)行了保護(hù),這樣保證了線程在規(guī)定時(shí)間后才喚醒。目前,Object.wait(long) 的文檔注釋聲明,它會(huì)在規(guī)定的時(shí)間“前后”蘇醒,所以線程有可能提前喚醒。在本例中,Timer 會(huì)讓另一個(gè) wait() 執(zhí)行(scheduledExecutionTime - System.currentTimeMillis())毫秒,從而保證任務(wù)永遠(yuǎn)不會(huì)提前執(zhí)行。任務(wù)是否會(huì)延遲執(zhí)行呢?會(huì)的。延遲執(zhí)行有兩個(gè)主要原因:線 程計(jì)劃和垃圾收集。
Java 語(yǔ)言規(guī)范故意沒(méi)有對(duì)線程計(jì)劃做嚴(yán)格的規(guī)定。這是因?yàn)?Java 平臺(tái)是通用的,并針對(duì)于大范圍的硬件及其相關(guān)的操作系統(tǒng)。雖然大多數(shù) JVM 實(shí)現(xiàn)都有公平的線程調(diào)度程序,但是這一點(diǎn)沒(méi)有任何保證 —— 當(dāng)然,各個(gè)實(shí)現(xiàn)都有不同的為線程分配處理器時(shí)間的策略。因此,當(dāng) Timer 線程在分配的時(shí)間后喚醒時(shí),它實(shí)際執(zhí)行其任務(wù)的時(shí)間取決于 JVM 的線程計(jì)劃策略,以及有多少其他線程競(jìng)爭(zhēng)處理器時(shí)間。因此,要減緩任務(wù)的延遲執(zhí)行,應(yīng)該將應(yīng)用程序中可運(yùn)行的線程數(shù)降至最少。為了做到這一點(diǎn),可以考慮在 一個(gè)單獨(dú)的 JVM 中運(yùn)行調(diào)度程序。
對(duì)于創(chuàng)建大量對(duì)象的大型應(yīng)用程序,JVM 花在垃圾收集(GC)上的時(shí)間會(huì)非常多。默認(rèn)情況下,進(jìn)行 GC 時(shí),整個(gè)應(yīng)用程序都必須等待它完成,這可能要有幾秒鐘甚至更長(zhǎng)的時(shí)間(Java 應(yīng)用程序啟動(dòng)器的命令行選項(xiàng) -verbose:gc 將導(dǎo)致向控制臺(tái)報(bào)告每一次 GC 事件)。要將這些由 GC 引起的暫停(這可能會(huì)影響快速任務(wù)的執(zhí)行)降至最少,應(yīng)該將應(yīng)用程序創(chuàng)建的對(duì)象的數(shù)目降至最低。同樣,在單獨(dú)的 JVM 中運(yùn)行計(jì)劃代碼是有幫助的。同時(shí),可以試用幾個(gè)微調(diào)選項(xiàng)以盡可能地減少 GC 暫停。例如,增量 GC 會(huì)盡量將主收集的代價(jià)分散到幾個(gè)小的收集上。當(dāng)然這會(huì)降低 GC 的效率,但是這可能是時(shí)間計(jì)劃的一個(gè)可接受的代價(jià)。
被計(jì)劃到什么時(shí)候?
如果任務(wù)本身能監(jiān)視并記錄所有延遲執(zhí)行的實(shí)例,那么對(duì)于確定任務(wù)是否能按時(shí)運(yùn)行會(huì)很有幫助。SchedulerTask 類(lèi)似于 TimerTask,有一個(gè) scheduledExecutionTime() 方法,它返回計(jì)劃任務(wù)最近一次執(zhí)行的時(shí)間。在任務(wù)的 run() 方法開(kāi)始時(shí),對(duì)表達(dá)式 System.currentTimeMillis() - scheduledExecutionTime() 進(jìn)行判斷,可以讓您確定任務(wù)延遲了多久執(zhí)行(以毫秒為單位)。可以記錄這個(gè)值,以便生成一個(gè)關(guān)于延遲執(zhí)行的分布統(tǒng)計(jì)。可以用這個(gè)值決定任務(wù)應(yīng)當(dāng)采取什么動(dòng) 作 —— 例如,如果任務(wù)太遲了,那么它可能什么也不做。在遵循上述原則的情況下,如果應(yīng)用程序需要更嚴(yán)格的時(shí)間保證,可參考 Java 的實(shí)時(shí)規(guī)范。
結(jié)束語(yǔ)
在本文中,我介紹了 Java 定時(shí)器框架的一個(gè)簡(jiǎn)單增強(qiáng),它使得靈活的計(jì)劃策略成為可能。新的框架實(shí)質(zhì)上是更通用的 cron —— 事實(shí)上,將 cron 實(shí)現(xiàn)為一個(gè) ScheduleIterator 接口,用以替換單純的 Java cron,這是非常有用的。雖然沒(méi)有提供嚴(yán)格的實(shí)時(shí)保證,但是許多需要計(jì)劃定期任務(wù)的通用 Java 應(yīng)用程序都可以使用這一框架。
參考資料
·下載本文中使用的 源代碼。
·“Tuning Garbage Collection with the 1.3.1 Java Virtual Machine”是 Sun 的一篇非常有用的文章,它給出了關(guān)于如何最小化 GC 暫停時(shí)間的提示。
·要獲得 developerWorks 中有關(guān) GC 的更多信息,請(qǐng)參閱以下文章:
“Java 理論與實(shí)踐:垃圾收集簡(jiǎn)史” (2003 年 10 月)。
“Mash that trash”(2003 年 7 月)。
“Fine-tuning Java garbage collection performance”(2003 年 1 月)。
“Sensible sanitation, Part 1”(2002 年 8 月)。
“Sensible sanitation, Part 2”(2002 年 8 月)。
“Sensible sanitation, Part 3”(2002 年 9 月)。
·在“Java 理論與實(shí)踐:并發(fā)在一定程度上使一切變得簡(jiǎn)單”(developerWorks, 2002 年 11 月)中,Brian Goetz 討論了 Doug Lea 的 util.concurrent 庫(kù),這是一個(gè)并發(fā)實(shí)用工具類(lèi)的寶庫(kù)。
·Brian Goetz 的另一篇文章“Threading lightly, Part 2: Reducing contention”(developerWorks,2001 年 9 月)分析了線程競(jìng)用以及如何減少它。
本文轉(zhuǎn)自:http://blog.csdn.net/zwhfyy/article/details/1620840
總結(jié)
以上是生活随笔為你收集整理的Java中如何实现每天定时对数据库的操作的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 帝国cms 未审核 showinfo.p
- 下一篇: Java防止用户同一时间重复登录(包括异