很多系统都需要一个在后台不间断运行的程序,以定期执行某些系统任务。这类似于Windows中自带的计划任务的功能。我一年半前我参与某省联通的网管项目的开发,曾经写过一个这样的后台程序,它要不间隔的从各种类型服务器上,下载各种类型的数据文件(每个文件都有几兆大小),并将这些文件解读成一条条记录插入到数据库中。这个后台程序直接使用java中的线程,由于线程的复杂性,调试也困难,很不幸这个后台程序很不稳定,每周都会有一两次会停在那里不再往下执行,原因至今天未找到,成为我心中永远的痛。

时隔今日,再次有幸参与IBM一个开发项目,这个项目同样需要一个类似的后台运行程序,这个程序的任务是:每隔一天检查数据库中的数据,并对符合某些条件记录进行某操作。任务很简单,为了今后扩展方便,我将这个设计成了一个多任务可管理的后台程序。周未我设置了两个任务同时执行,一任务每10秒执行一次,另一任务每1秒执行一行,运行了两天,运行较果良好。估计很多朋友会面临与我同样的问题,在此将程序思路和代码公布,希望有兴趣的朋友大家一起研究探讨。

程序开发环境: 
       使用Java(JDK 1.4)开发,图形界面使用Eclipse (2.1.3版)的SWT方式开发。运行主机:P4 2.6+1G内存 windowsXP操作系统

预备。 
       开发此类程序,最好不要直接使用JAVA的线程来编程,这样会增加不必要的复杂度和难度,吃力不讨好。在JAVA中有一个包 java.util.Timer 这个包封装了对线程的操作,我们可以把它称做定时器类。我们先来看一个简单例子: 
import java.util.Timer; 
import java.util.TimerTask; 
public class Reminder { 
    Timer timer; 
    public Reminder(int seconds) { 
        timer = new Timer(); 
        timer.schedule(new RemindTask(),seconds*1000); //参数要求转化成毫秒 
    } 
    public static void main(String args[]) { 
        new Reminder(5); //5秒后运行 
    } 
    /**一个内部类,封装了所要运行的任务*/ 
    class RemindTask extends TimerTask { 
        public void run() { 
            System.out.println("任务运行。。。。"); 
            timer.cancel(); //结束timer中的所有任务 
        } 
    } 
}

  这里涉及于两个JAVA类Timer和TimerTask。我们继承TimerTask类后,将所要运行的任务封装其run方法中;Timer可以管理几千个任务(TimerTask),注意,同一个任务对象不能两次加入到Timer中执行。 
对(虽然执行的任务都一样,但是两个任务对象): 
timer.schedule(new RemindTask(), seconds * 1000); 
timer.schedule(new RemindTask(), seconds * 1000);

错 
RemindTask task= new RemindTask(); 
timer.schedule(task, seconds * 1000); 
timer.schedule(task, seconds * 2000);

设计方案。

说明: 
  任务类的设计。我们先创建一个抽象类AbstractTimerTask,这个类直接继承至TimerTask类,提供对TimerTask封装。然后所有具体的任务类(如:TimerTask_1)继承自AbstractTimerTask。 
import java.util.TimerTask; 
public abstract class AbstractTimerTask extends TimerTask { 
    TaskEntry taskEntry; //任务记录 
    public AbstractTimerTask(TaskEntry taskEntry) { 
        this.taskEntry = taskEntry; 
    } 
    /* 
     * 生成一个新的实例相当于克隆自身;原因在于:同一任务对象不能两次加入到Timer 
     * 在TaskEntry类将看到它的使用方法 
     */ 
    abstract AbstractTimerTask getCloneObject(); 
}

下面是它的一个实现类的源代码我们可以将要运行任务的代码写在这个类中。 
import java.util.Calendar; 
public class TimerTask_1 extends AbstractTimerTask { 
    public TimerTask_1(TaskEntry taskEntry) { //构造方法 
        super(taskEntry); 
    } 
    public AbstractTimerTask getCloneObject() {   
        return new TimerTask_1(taskEntry); 
    } 
    public void run() { 
        /*在这里写你要执行的程序。。。。。*/ 
        System.out.println("??时:"+taskEntry.getName()+"运行了一次"); 
        this.taskEntry.taskStart(); //运行下一个时间点任务 
    } 
}

在AbstractTimerTask类有一个TaskEntry字段,这是本设计的一个核心类,它代表一条封装完整的任务记录,每个任务类和它的运行计划都封装在这条类中,源代码如下。Timer和AbstractTimerTask前面都已说过,那么TimePlan是做什么用的呢? 
import java.util.Calendar; 
import java.util.Date; 
import java.util.Timer; 
import mytimer.util.Util; 
/**任务记录类*/ 
public class TaskEntry { 
    public static final int TASK_START = 0; //定义两个表示任务记录状态常量 
    public static final int TASK_STOP = 1; 
    private Long oid; //任务ID号,唯一 
    private String name; //任务名称 
    private int state = TASK_STOP; //任务状态(启动/停止) 
    private Timer timer; //JAVA计时器 
    private TimePlan timePlan; //时间计划的类型 
    private AbstractTimerTask timerTask; //任务类的种子对象,由这个对象来不断克隆 
    private AbstractTimerTask runTimerTask; //运行计划的当前任务 
    /** 
       * ITaskEntry.taskStart()-->TimerTask.run()-->ITaskEntry.taskStart() 
       * 形成一个循环回路。本方法负责起动本类代表的任务 
       */ 
    public void taskStart() { 
        if (timePlan.haveNext()) { 
            Date date = timePlan.nextDate();//得到任务计划时间 
            runTimerTask = timerTask.getCloneObject();//得到任务(复制) 
            timer.schedule(runTimerTask, date); //加入计划队列 
            //打印将要运行的计划任务的信息 
            Calendar c = Calendar.getInstance(); 
            c.setTimeInMillis(runTimerTask.scheduledExecutionTime()); 
            System.out.println(Util.dateToLongStr(c.getTime())+"将运行"+name); 
        } else { 
            state = TASK_STOP; 
            System.out.println(name + "结束"); 
        } 
    } 
     /**停止任务*/ 
    public void taskStop() { 
        if (runTimerTask != null) { 
            //打印信息 
            Calendar c = Calendar.getInstance(); 
            c.setTimeInMillis(runTimerTask.scheduledExecutionTime()); 
            System.out.println("计划于:"+Util.dateToLongStr(c.getTime())+"运行的" + name + "被终止"); 
            //终止本任务, 调用Timer.cancel()是终止Timer的所有任务。 
            runTimerTask.cancel(); 
        } else { 
            System.out.println(name + "未进入执行计划"); 
        } 
    } 
 ……… 一些属性的get/set方法(省略) 
    /** 监听类(内部类) */ 
    public static class DateBeforeTodayException extends NullPointerException { 
        private Date date; 
        public DateBeforeTodayException(Date date) {this.date = date;} 
        public String toString() { 
            return "计划时间(" + Util.dateToLongStr(date) + ")早于当前时间"; 
        } 
    } 
}

1、TimePlan是一个接口(interface),它是表示“运行计划的方案”,这个程序中提供了三种运行计划方案(见前图:计划任务的设置界面): 
一次性运行。 
每隔一个时间段运行。 
一周中选择那几天运行。  
将它设计成一个接口是为了方便今后扩展,如果要新增新的时间方案只需要继承这个接口写一个新的实现即可。三种时间方案的类图如下:

说明: 
a) TimePlan封装了五个方法,其它haveNext()和nextDate()最重要,这个两个方法模仿了Java中集合类(Collection)的迭代器(Iterator)的设计形式,代码如下: 
import java.util.Date; 
//时间计划方案的接口 
public interface TimePlan { 
boolean haveNext();//判断还有没有下一个计划时间 
    Date nextDate();//得到下一个计划时间 
    Date getCurrentDate();//得到开始时间 
    void setCurrentDate(Date date); //设计开始时间 
    String getTimePlanString();//显示运行计划方案的文字说明 
}

b) AbstractTimePlan是这个抽象类,主要目的是将一些各子类的公共方法写在这里。代码如下: 
import java.util.Date; 
public abstract class AbstractTimePlan implements TimePlan { 
    //记录计划的第一时间点,除设置新的起始时间,否则不再改变 
  protected Date currentDate; 
/* 
当前计划的时间点,每次计划替换时被更新, 
似乎这个才应叫cureentDate,sorry不想再改了 
*/ 
  protected Date planDate;

public boolean haveNext() { 
      return (planDate != null); 
  } 
  public Date getCurrentDate() { 
      return currentDate; 
  } 
  public void setCurrentDate(Date date) { 
      currentDate = date; 
      planDate = date; //在赋给currentDate值时,同时也赋给planDate 
  } 
}

c) 然后我们看看三种计划方案的实现类的源代码: 
//“一次性运行”的计划方案类 
import java.util.Date; 
public class TimePlanOnce extends AbstractTimePlan { 
    public Date nextDate() { 
//把要当前的计划时间保存在中间变量中 
        Date returnDate = this.planDate; 
//算出下一个计划时间。没有下一个就设为null 
        this.planDate = null; 
    //判断一下计划时间合不合条件 
        if (returnDate == null) 
            throw new NullPointerException("没有下一个计划日期"); 
        return returnDate; 
    }

public String getTimePlanString() { 
        return "一次性运行,运行时间: (打印this.currentDate) "; 
    } 
}

//“周期性间隔”的时间计划方案类 
import java.util.Date; 
public class TimePlanPeriod extends AbstractTimePlan { 
public static final int HOUR = 0; 
public static final int DAY = 1;

private int spaceTime; //间隔时间,单位毫秒 
private int timeType;

public Date nextDate() { 
//把要当前的计划时间保存在中间变量中 
     Date returnDate = this.planDate; 
//算出下一个计划时间。没有下一个就设为null 
     int milliSecond = 0; 
     if (timeType ==HOUR) milliSecond = spaceTime * 1000; //小时*60*60*1000; 
     if (timeType ==DAY) milliSecond = spaceTime * 24 * 60 * 60 * 1000; //天 
     planDate = Util.DateAddSpaceMilliSecond(planDate, milliSecond); 
     //判断一下计划时间合不合条件 
     if (returnDate == null) 
         throw new NullPointerException("没有下一个计划日期"); 
     return returnDate; 

public String getTimePlanString() { 
     if (timeType == HOUR) 
         return "第一次运行于:currentDate.并每隔spaceTime小时运行一次"; 
     if (timeType == DAY) 
         return "第一次运行于:currentDate.并每隔spaceTime天运行一次"; 
     return ""; 
}

public int getSpaceTime() {    return spaceTime; } 
public int getTimeType() { return timeType;   } 
public void setSpaceTime(int i) { spaceTime = i; } 
public void setTimeType(int i) {  timeType = i; } 
}

/**选择一周的某几天,让这几天在同一时间点运行任务, 一周内必须选择一天*/ 
import java.util.Calendar; 
import java.util.Date; 
public class TimePlanSelectWeek extends AbstractTimePlan { 
   private static Calendar c = Calendar.getInstance(); //取得一个日历实例 
   private static int spaceMilliSecond = 0; //间隔时间,单位毫秒 
   private boolean[] selectWeek = new boolean[7]; //0为星期日 ,1为星期一

public Date nextDate() { 
       Date returnDate = null; 
if (!isSelectWeek(planDate)) //如果这一天不是所选周中的一天 
           planDate = getNextDate(planDate); 
       returnDate = planDate; 
       planDate = getNextDate(planDate); 
//判断一下计划时间合不合条件 
       if (returnDate == null) 
           throw new NullPointerException("没有下一个计划日期"); 
       return returnDate; 
   } 
//算出下一个计划时间。没有下一个就设为null 
   private Date getNextDate(Date date) { 
       Date tempDate = date; 
       Date returnDate = null; 
       for (int i = 0; i < 7; i++) { 
           tempDate = Util.DateAddSpaceMilliSecond(tempDate, spaceMilliSecond); 
           if (isSelectWeek(tempDate)) { 
               returnDate = tempDate; 
               break; 
           } 
       } 
       return returnDate; 
   }

/**设置某星期是否被选, 0为星期日 ,1为星期一....6为星期六*/ 
   public void setSelectWeek(int i, boolean b) {selectWeek[i] = b;} 
   /** 判断某星期是否被选*/ 
   public boolean isSelectWeek(int i) {return selectWeek[i];} 
   /**判断某天所属星期几是否被选*/ 
   public boolean isSelectWeek(Date date) { 
       if (date == null) return false; 
       c.setTime(date); 
       //Calendar.DAY_OF_WEEK:星期日=1,星期六=7 c.get(Calendar.DAY_OF_WEEK) 
       return isSelectWeek(c.get(Calendar.DAY_OF_WEEK) - 1); 
   } 
   public String getTimePlanString() { 
       StringBuffer sb = new StringBuffer(""); 
       if (selectWeek[1]) sb.append("周一,"); 
       if (selectWeek[2]) sb.append("周二,"); 
       if (selectWeek[3]) sb.append("周三,"); 
       if (selectWeek[4]) sb.append("周四,"); 
       if (selectWeek[5]) sb.append("周五,"); 
       if (selectWeek[6]) sb.append("周六,"); 
       if (selectWeek[0]) sb.append("周日,"); 
       return "每周的"+sb.toString()+"运行"; 
   } 
}

TimerTask的工厂类。将生成TimerTask的代码另起一个类的好处是代码的层次比较清楚,也比较好管理。由于TimerTask包含有几个字段,因此产生一个TimerTask对象还是有一定的复杂度,建立一个专门生成TimerTask的工厂类,这样我们在生成一个TimerTask对象时就可以少掉很多麻烦的代码了。当然由于我的工作任务,只需要一个TimerTask对象就够了,所以最初之前我是将它直接写在图形界面的代码里的。

  这里建立一个TimerTask对象池tasks,它是一个静态变量,这样在getInstance时不必总是要新生成一个TimerTask。还有Timer也是一个静态变量,它是一个全局单例(是最简单的单例模式了),因为前面说了Timer可以管理几千个任务,所以Timer对象一个就够了。 
import java.util.HashMap; 
import java.util.Timer; 
public class TaskEntryFactory { 
    private static final HashMap tasks = new HashMap(); 
    private static final Timer timer = new Timer(); 
    public static TaskEntry getInstance(Long oid, String name) { 
        if (tasks.containsKey(oid)) { 
            return (TaskEntry) tasks.get(oid); 
        } else { 
            TaskEntry entry = new TaskEntry(); 
            entry.setOid(oid); 
            entry.setName(name); 
            entry.setTimer(timer); 
            entry.setTimerTask(new TimerTask_1(entry)); 
            tasks.put(oid, entry); 
            return entry; 
        } 
    } 
}

起动和停止任务,当“任务设置界面(TaskListDialog.java)”点击OK后处理。界面的编写就不在本文讨论的范围内了。 
//任务设置界面中点击”确认(OK)”按钮后的处理 
if (dialog.open() == Window.OK) { 
    if (taskEntry.getState() == TaskEntry.TASK_START) { 
        taskEntry.taskStop();//将旧的停掉 
        taskEntry.taskStart();//开始新设置的 
    } 
if (taskEntry.getState() == TaskEntry.TASK_STOP) 
        taskEntry.taskStop(); 
    tv.refresh(taskEntry); 
}

一个Java后台程序的实例相关推荐

  1. CentOS7+运行/停止Java后台程序,问题处理

    CentOS7+启动/关闭Java后台程序 1.启动 # 以服务方式在后台运行,退出命令窗口不关闭 nohup java -jar XXX.jar >jarRun.log 2>&1 ...

  2. java的应用程序开发_开发一个Java应用程序(1)

    开发一个Java应用程序(1) App Engine上的Java Web应用程序通过Java Servlet标准接口与应用程序服务器交互.一个应用程序由一个或多个类组成,这些类都扩展自一个servle ...

  3. IntelliJ IDEA 运行你的第一个Java应用程序

    IntelliJ IDEA 运行你的第一个Java应用程序 创建项目让我们创建一个简单的Java Hello World项目. 单击创建新的项目. 打开新建项目向导. 你应该注意的主要是项目的SDK. ...

  4. java 获取文件所在的文件夹_带你0基础编写一个Java小程序,领略Java程序从编写到编译再到运行的全流程...

    在学习Java之前我们需要先认识下什么是计算机语言?计算机语言又有哪些分类?在了解这些后对我们理解学习帮助是很大的. 要知道计算机语言是人与计算机之间进行信息交流沟通的一种特殊语言,又分为机械语言.汇 ...

  5. java基础,继承类题目:编写一个Java应用程序,该程序包括3个类:Monkey类、People类和主类 E...

    21.编写一个Java应用程序,该程序包括3个类:Monkey类.People类和主类 E.要求: (1) Monkey类中有个构造方法:Monkey (String s),并且有个public vo ...

  6. 10. 我的第一个Java应用程序

    10. 我的第一个Java应用程序 public class HelloWorld {            public static void main(String[] args)       ...

  7. 编写一个Java应用程序,从键盘读取用户输入两个字符串,并重载3个函数分别实现这两个字符串的拼接、整数相加和浮点数相加。要进行异常处理,对输入的不符合要求的字符串提示给用户,不能使程序崩溃。

    编写一个Java应用程序,从键盘读取用户输入两个字符串,并重载3个函数分别实现这两个字符串的拼接.整数相加和浮点数相加.要进行异常处理,对输入的不符合要求的字符串提示给用户,不能使程序崩溃. pack ...

  8. java程序步骤_java编写程序的步骤是什么?java编写程序步骤实例讲解

    写java编程就是一步一步的来,这样才会写好一个编程,之后也才能正常的运行,那么java编写程序的步骤是什么?今天我们就来给大家讲解一下这方面的内容.大家可以参考以下文章! 1.编写源文件 使用文本编 ...

  9. 左右植树java_Plant 模拟植树活动,编写一个java应用程序 联合开发网 - pudn.com

    Plant 所属分类:Java编程 开发工具:Java 文件大小:1KB 下载次数:3 上传日期:2011-03-20 17:42:26 上 传 者:大宝 说明:  模拟植树活动,编写一个java应用 ...

最新文章

  1. 活动报名 | 智源重大研究方向:自然语言处理暨“北京智源-京东跨媒体对话智能联合实验室”发布会...
  2. vba 修改access表的链接地址_VBA中常用的这7种数据类型,你都get到了吗?
  3. shell脚本一键安装JDK及配置环境变量
  4. Python操纵Mysql数据库的三种方法,实现增删改查
  5. mysql存储过程打不开了_请问mysql存储过程的问题,我找了几个例子一个都运行不起来,...
  6. Linux查看内存占用--free
  7. 在IE7 中遇到的几个小问题,有解决方案
  8. leetcode 423 从英文中重建数字
  9. Win10 激活工具被删除或“无法成功完成操作,因为文件包含病毒或潜在的垃圾软件”
  10. 图片自适应手机横屏竖屏的宽高
  11. 2018年清华美院交叉学科保研面试经验
  12. WORD之文字处理之页眉页脚的设置
  13. 动态规划实例--数组不连续取数问题(python 实现)
  14. BPDU Timers
  15. luckysheet 只读模式和编辑模式
  16. 一行代码实现IOS 3DES加密解密
  17. StarUML用户手册
  18. iOS获取文件夹下所有的文件
  19. 苹果几是双卡双待_有点不可思议,苹果iPhone 11:真的是支持双卡双待
  20. 使用Javascript Rhino重载Java方法

热门文章

  1. js获取7天前,n天前的日期,7天后,n天后的日期,获取当前日期是周几
  2. opencv 扩大区域_Android 扩大 View 的点击区域的方法
  3. 【喜讯】美的5G+蓝牙AOA融合精准定位项目,入选国家工业和信息化部,创新十大场景应用案例
  4. C语言数据结构单项链表
  5. 益和VA市场发展良好,产品稳定两增一减
  6. H5的新特性及API详解(很惊人)
  7. Web3.js API 中文文档
  8. HUAWEI Mate 40 Pro 详细配置
  9. 安装rabbitmq图文详情记录
  10. 目前中国大陆手机号码正确格式正则表达式