[转载]防止JAVA程序重复启动的一个另类解决办法

我们项目中有一个后台任务处理程序,是java开发application,用以处理网站提交的一些批量数据文件,因为这些数据文件数据量一般都比较大,所以写了这个批量处理程序,用以异步处理这些批量数据文件。这个程序设计成插件式的,处理各种不同数据文件的功能单独作为一个插件,然后使用Spring来粘合各个组件,这样就可以很方便地对该程序进行扩展。
今天客户提出一个要求:需要控制这个程序在同一主机上只能启动一个实例。
为了实现客户要求,我首先想到就是在数据库中建一张表,程序启动时往该表中写入一个标志,等程序结束时再删除标志。但这种方式存在一个问题就是,如果程序是非正常停止或被杀进程,那么这个标志就不可能被清除,那下一次启动就会误判为重复启动;另外,如果用数据库来记录启动标志的话,还把该程序跟数据库紧密耦合起来,感觉很别扭。
排除了第一种方案之后,我以想到了用文件来保存启动标志(好象一些大型的程序,诸如weblogic好象就是采用在文件中记录启动标志方式来控制重复启动的)。客流量然这种方式不需要与数据库耦合在一起,但也存在程序异常中止而无法清除启动标志的问题,所以这个方案也被枪毙了。
我想到的第三种方案就是在JAVA中调用操作系统的查看系统进程的方式来取得系统进程,然后再检测系统进程有特殊的进程标志来判断是否重复启动。但这种方式一是看起来很别扭,再者就是Window和 *nix系统中查看系统进程的命令不一样,分成几种情况来处理,无端地增加了程序的复杂性,也不可取。
能不能在内存中记录一个启动标志呢?理论上这应该是不可行的,因为跨JVM来相互操作内存数据是不可能。我在网上搜了一下,也没找到相关的例子。
那能不能占用一点系统共享资源,来换取我们的目标呢?比较容易想到的系统资源并且不能重复使用的资源就是端口。我尝试采用如下方案:在程序中指定一个不常用的端口(比如:12345),在程序启动时,就指定的端口启动一个ServerSocket,这个Socket只是为了占用这个端口,不接受任何网络连接。如果试图启动第二个实例时,程序在该指定端口启动ServerSocket时就会抛异常,这时我们就可以认为系统已经启动过了,然后打印提示并直接退出程序即可。这种方式在理论上分析应该可以的,我开始动手修改程序。程序修改如下:
java 代码
  1. package cn.com.pansky.xmdswz.application.scheduler;
  2. import org.apache.commons.logging.Log;
  3. import org.apache.commons.logging.LogFactory;
  4. import org.quartz.SchedulerException;
  5. import org.quartz.impl.StdScheduler;
  6. import org.springframework.beans.factory.BeanFactory;
  7. import org.springframework.context.support.ClassPathXmlApplicationContext;
  8. import cn.com.pansky.xmdswz.system.cache.CachedTableMgr;
  9. import cn.com.pansky.xmdswz.system.config.SystemConfig;
  10. import cn.com.pansky.xmdswz.utility.DateUtil;
  11. import org.quartz.JobDetail;
  12. import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
  13. import java.net.ServerSocket;
  14. import java.io.*;
  15. /**
  16. * Title: XXXXXXX
  17. * Description: XXXXXXXXXXXX
  18. * Copyright: Copyright (c) 2006
  19. * Company: www.pansky.com.cn
  20. *
  21. * @author Sheng Youfu
  22. * @version 1.0
  23. */
  24. public class Scheduler {
  25. private static Log log = LogFactory.getLog(Scheduler.class);
  26. private static ServerSocket srvSocket = null; //服务线程,用以控制服务器只启动一个实例
  27. private static final int srvPort = 12345; //控制启动唯一实例的端口号,这个端口如果保存在配置文件中会更灵活
  28. /**
  29. * 定时任务配置文件
  30. */
  31. private static String CONFIG_FILE = "cn/com/pansky/xmdswz/application/scheduler/Scheduling-bean.xml";
  32. public Scheduler() {
  33. //检测系统是否只启动一个实例
  34. checkSingleInstance();
  35. //下面读取Spring的配置文件
  36. SystemConfig cfg = new SystemConfig();
  37. String config = cfg.parseParam("SCHEDULER.CONFIG_FILE", false);
  38. if(config!=null && !"".equals( config.trim()))
  39. CONFIG_FILE = config;
  40. log.debug("CONFIG_FILE: "+CONFIG_FILE);
  41. }
  42. /**
  43. * 主函数
  44. * @param args String[]
  45. * @throws Exception
  46. */
  47. public static void main(String[] args) throws Exception{
  48. Scheduler sch = new Scheduler();
  49. sch.execute();
  50. }
  51. /**
  52. * 运行定时任务
  53. */
  54. public void execute() {
  55. ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext(new String[] {CONFIG_FILE});
  56. BeanFactory factory = (BeanFactory) appContext;
  57. /**
  58. * 装载任务调度
  59. */
  60. StdScheduler scheduler = (StdScheduler) factory.getBean("schedulerFactoryBean");
  61. //先暂停所有任务,等待装载缓存代码表
  62. try {
  63. scheduler.pauseAll();
  64. } catch (SchedulerException ex) {
  65. log.error("",ex);
  66. }
  67. /**
  68. * 装载缓存代码表
  69. */
  70. CachedTableMgr cachedtableMgr = (CachedTableMgr) factory.getBean("cachedTableMgr");
  71. try {
  72. cachedtableMgr.loadCodeTable();
  73. } catch (Exception ex) {
  74. log.fatal("Load cached table failed. System will exit.", ex);
  75. System.exit(0);
  76. }
  77. //重新恢复所有任务
  78. try {
  79. scheduler.resumeAll();
  80. } catch (SchedulerException ex) {
  81. log.error("",ex);
  82. }
  83. }
  84. /**
  85. * 检测系统是否只启动了一个实例
  86. */
  87. protected void checkSingleInstance() {
  88. try {
  89. srvSocket = new ServerSocket(srvPort); //启动一个ServerSocket,用以控制只启动一个实例
  90. } catch (IOException ex) {
  91. if(ex.getMessage().indexOf("Address already in use: JVM_Bind")>=0)
  92. System.out.println("在一台主机上同时只能启动一个进程(Only one instance allowed)。");
  93. log.fatal("", ex);
  94. System.exit(0);
  95. }
  96. }
  97. }
[@more@]

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/443058/viewspace-915510/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/443058/viewspace-915510/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值