`

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

阅读更多

         我们项目中有一个后台任务处理程序,是java开发application,用以处理网站提交的一些批量数据文件,因为这些数据文件数据量一般都比较大,所以写了这个批量处理程序,用以异步处理这些批量数据文件。这个程序设计成插件式的,处理各种不同数据文件的功能单独作为一个插件,然后使用Spring来粘合各个组件,这样就可以很方便地对该程序进行扩展。
        今天客户提出一个要求:需要控制这个程序在同一主机上只能启动一个实例。
        为了实现客户要求,我首先想到就是在数据库中建一张表,程序启动时往该表中写入一个标志,等程序结束时再删除标志。但这种方式存在一个问题就是,如果程序是非正常停止或被杀进程,那么这个标志就不可能被清除,那下一次启动就会误判为重复启动;另外,如果用数据库来记录启动标志的话,还把该程序跟数据库紧密耦合起来,感觉很别扭。
        排除了第一种方案之后,我以想到了用文件来保存启动标志(好象一些大型的程序,诸如weblogic好象就是采用在文件中记录启动标志方式来控制重复启动的)。客流量然这种方式不需要与数据库耦合在一起,但也存在程序异常中止而无法清除启动标志的问题,所以这个方案也被枪毙了。
        我想到的第三种方案就是在JAVA中调用操作系统的查看系统进程的方式来取得系统进程,然后再检测系统进程有特殊的进程标志来判断是否重复启动。但这种方式一是看起来很别扭,再者就是Window和 *nix系统中查看系统进程的命令不一样,分成几种情况来处理,无端地增加了程序的复杂性,也不可取。
        能不能在内存中记录一个启动标志呢?理论上这应该是不可行的,因为跨JVM来相互操作内存数据是不可能。我在网上搜了一下,也没找到相关的例子。
        那能不能占用一点系统共享资源,来换取我们的目标呢?比较容易想到的系统资源并且不能重复使用的资源就是端口。我尝试采用如下方案:在程序中指定一个不常用的端口(比如:12345),在程序启动时,就指定的端口启动一个ServerSocket,这个Socket只是为了占用这个端口,不接受任何网络连接。如果试图启动第二个实例时,程序在该指定端口启动ServerSocket时就会抛异常,这时我们就可以认为系统已经启动过了,然后打印提示并直接退出程序即可。这种方式在理论上分析应该可以的,我开始动手修改程序。程序修改如下:

java 代码
  1. package cn.com.pansky.xmdswz.application.scheduler;   
  2.   
  3. import org.apache.commons.logging.Log;   
  4. import org.apache.commons.logging.LogFactory;   
  5. import org.quartz.SchedulerException;   
  6. import org.quartz.impl.StdScheduler;   
  7. import org.springframework.beans.factory.BeanFactory;   
  8. import org.springframework.context.support.ClassPathXmlApplicationContext;   
  9. import cn.com.pansky.xmdswz.system.cache.CachedTableMgr;   
  10. import cn.com.pansky.xmdswz.system.config.SystemConfig;   
  11. import cn.com.pansky.xmdswz.utility.DateUtil;   
  12. import org.quartz.JobDetail;   
  13. import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;   
  14. import java.net.ServerSocket;   
  15. import java.io.*;   
  16.   
  17. /**  
  18.  * Title: XXXXXXX 
  19.  * Description: XXXXXXXXXXXX 
  20.  * Copyright: Copyright (c) 2006 
  21.  * Company: www.pansky.com.cn 
  22.  *  
  23.  * @author Sheng Youfu  
  24.  * @version 1.0  
  25.  */  
  26. public class Scheduler {   
  27.   private static Log log = LogFactory.getLog(Scheduler.class);   
  28.   
  29.   private static ServerSocket srvSocket = null//服务线程,用以控制服务器只启动一个实例   
  30.   
  31.   private static final int srvPort = 12345;     //控制启动唯一实例的端口号,这个端口如果保存在配置文件中会更灵活   
  32.   
  33.   /**  
  34.    * 定时任务配置文件  
  35.    */  
  36.   private static String CONFIG_FILE = "cn/com/pansky/xmdswz/application/scheduler/Scheduling-bean.xml";   
  37.   
  38.   
  39.   public Scheduler() {   
  40.     //检测系统是否只启动一个实例   
  41.     checkSingleInstance();   
  42.   
  43.     //下面读取Spring的配置文件   
  44.     SystemConfig cfg = new SystemConfig();   
  45.     String config = cfg.parseParam("SCHEDULER.CONFIG_FILE"false);   
  46.     if(config!=null && !"".equals( config.trim()))   
  47.       CONFIG_FILE = config;   
  48.     log.debug("CONFIG_FILE: "+CONFIG_FILE);   
  49.   }   
  50.   
  51.   /**  
  52.    * 主函数  
  53.    * @param args String[]  
  54.    * @throws Exception  
  55.    */  
  56.   public static void main(String[] args) throws Exception{   
  57.     Scheduler sch = new Scheduler();   
  58.     sch.execute();   
  59.   }   
  60.   
  61.   /**  
  62.    * 运行定时任务  
  63.    */  
  64.   public void execute() {   
  65.     ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext(new String[] {CONFIG_FILE});   
  66.     BeanFactory factory = (BeanFactory) appContext;   
  67.   
  68.     /**  
  69.      * 装载任务调度  
  70.      */  
  71.     StdScheduler scheduler = (StdScheduler) factory.getBean("schedulerFactoryBean");   
  72.     //先暂停所有任务,等待装载缓存代码表   
  73.     try {   
  74.       scheduler.pauseAll();   
  75.     } catch (SchedulerException ex) {   
  76.       log.error("",ex);   
  77.     }   
  78.   
  79.     /**  
  80.      * 装载缓存代码表  
  81.      */  
  82.     CachedTableMgr cachedtableMgr = (CachedTableMgr) factory.getBean("cachedTableMgr");   
  83.     try {   
  84.       cachedtableMgr.loadCodeTable();   
  85.     } catch (Exception ex) {   
  86.       log.fatal("Load cached table failed. System will exit.", ex);   
  87.       System.exit(0);   
  88.     }   
  89.   
  90.     //重新恢复所有任务   
  91.     try {   
  92.       scheduler.resumeAll();   
  93.     } catch (SchedulerException ex) {   
  94.       log.error("",ex);   
  95.     }   
  96.   }   
  97.   
  98.   /**  
  99.    * 检测系统是否只启动了一个实例  
  100.    */  
  101.   protected void checkSingleInstance() {   
  102.     try {   
  103.       srvSocket = new ServerSocket(srvPort); //启动一个ServerSocket,用以控制只启动一个实例   
  104.     } catch (IOException ex) {   
  105.       if(ex.getMessage().indexOf("Address already in use: JVM_Bind")>=0)   
  106.         System.out.println("在一台主机上同时只能启动一个进程(Only one instance allowed)。");   
  107.       log.fatal("", ex);   
  108.       System.exit(0);   
  109.     }   
  110.   }   
  111. }   


经过测试,程序能很好地满足我们的要求,问题解决。
         我之所以称这种方式另类,是因为这种方式以牺牲一个端口的代价来达到我们的设计要求,采用这种方式的人应该不多。但我认为,只要我们牺牲的代价与我们的目标比较起来是在可接受的范围内,这种方式就是可取的,这与我们花钱增加内存来让程序运行更快在本质应该是相同的。

分享到:
评论
23 楼 hiswing 2006-12-20  
yongweiking 写道
hiswing 写道
巧了.这个思路我N年前也用过.
http://www.mxjava.com/blog/article.asp?id=3

而且最近公司的项目中也用了这个思路.呵呵~~我们都在杭州哦!



看了你写的方法,但我有一点疑问,你在选择服务器端口时,采用的是自动增加的方法,这个方法没问题,但你在console类中,iPort = 50000。由于在不同的jvm下运行,这个Consonle客户端每次连接的都是端口50000,那么,Server中端口增加非但没有意义,反而会引起误操作。假如端口50000被其它程序占用,那么该程序还不能很好做到防止重复运行了。我想还是指定端口吧。望指教。


我想你没有搞懂这个程序是做什么的。建议再认真看一遍。
22 楼 Jboss 2006-12-11  
你这个方法太old了,前几年我刚学java时就有人提过这个方法了,并不是什么“另类解决办法”。
21 楼 zgd 2006-12-11  
不另类,两个方法
首选nio锁文件
次选socket端口
已经是公认的解决方案了
20 楼 coolyzg 2006-12-11  
可以使用单例模式的一种变种
 if(instance != null) return "在一台主机上同时只能启动一个进程(Only one instance allowed)。";
19 楼 ashin 2006-12-11  
可以用访问记录。
定时记录访问时间到文件,
超时即为无程序运行或程序导常。
这样不是更容易些?
17 楼 yongweiking 2006-12-09  
hiswing 写道
巧了.这个思路我N年前也用过.
http://www.mxjava.com/blog/article.asp?id=3

而且最近公司的项目中也用了这个思路.呵呵~~我们都在杭州哦!



看了你写的方法,但我有一点疑问,你在选择服务器端口时,采用的是自动增加的方法,这个方法没问题,但你在console类中,iPort = 50000。由于在不同的jvm下运行,这个Consonle客户端每次连接的都是端口50000,那么,Server中端口增加非但没有意义,反而会引起误操作。假如端口50000被其它程序占用,那么该程序还不能很好做到防止重复运行了。我想还是指定端口吧。望指教。
16 楼 wuyunlong 2006-12-08  
wdmsyf 写道
引用

Nirvana     1 小时前
我觉得利用文件来保存启动标记也可以,只是思路变换一下,打开后一个实例,强制关闭前一个:
启动实例时生成唯一标记,保存变量,写入文件,然后循环读取文件内容,判断是否与保存变量相等,不等则退出。
第二实例运行时因操作同一文件,必然会满足第一实例不等的条件,迫使第一实例关闭。

刚才做了个小实验,发现也可行。

我个人觉得Nirvana的方法不是很可行,你说在第二实例启动时,强制关闭第一实例,这存在两个问题:
1、如何做到从第二实例中关闭第一实例?
2、即使可以关闭第一实例,那第一实例中正在处理的数据咋办?而且这样强制关闭第一实例,感觉有点象病毒噢,呵呵!


我也倾向于通过锁定文件来保证java启动程序的唯一性,以前我写过一个这样的mini程序库。
关闭第一个实例是通过在程序中添加JMX接口,允许远程调用该接口来关闭程序,而不是执行kill -9这样的操作
来关闭。
15 楼 wdmsyf 2006-12-08  
引用
hiswing     7 小时前

巧了.这个思路我N年前也用过.
http://www.mxjava.com/blog/article.asp?id=3

而且最近公司的项目中也用了这个思路.呵呵~~我们都在杭州哦!

hiswing:呵呵,与我心有戚戚焉,握个手先!  
14 楼 kenny319 2006-12-08  
之前也碰到过类似的问题,这的确是一个比较好的方式。
13 楼 hiswing 2006-12-08  
巧了.这个思路我N年前也用过.
http://www.mxjava.com/blog/article.asp?id=3

而且最近公司的项目中也用了这个思路.呵呵~~我们都在杭州哦!
12 楼 Nirvana 2006-12-08  
wdmsyf 写道
引用

Nirvana     1 小时前
我觉得利用文件来保存启动标记也可以,只是思路变换一下,打开后一个实例,强制关闭前一个:
启动实例时生成唯一标记,保存变量,写入文件,然后循环读取文件内容,判断是否与保存变量相等,不等则退出。
第二实例运行时因操作同一文件,必然会满足第一实例不等的条件,迫使第一实例关闭。

刚才做了个小实验,发现也可行。

我个人觉得Nirvana的方法不是很可行,你说在第二实例启动时,强制关闭第一实例,这存在两个问题:
1、如何做到从第二实例中关闭第一实例?
2、即使可以关闭第一实例,那第一实例中正在处理的数据咋办?而且这样强制关闭第一实例,感觉有点象病毒噢,呵呵!


1.如果觉得不清楚,补充一下:第二个实例运行时,改变了同一文件所记录的启动标记,而第一个实例始终循环读取文件进行比较,此时标记变量值与文件内容不等即关闭。
这个应该很简单的。

2.请问你非正常情况下退出时,数据是怎么处理的?难道都是随它去吗?
另外,我的这关闭,应该是正常退出,尚在程序可控制范围下。
扯病毒就太远了,倒是服务器端口经常被封的说。
11 楼 Tanner 2006-12-08  
File lock 要指定文件路径,很局限,还是端口更全局一些。
10 楼 Tanner 2006-12-08  
http://www.google.com/codesearch?hl=en&q=+lang:java+file+lock+show:c7KnypL3_Ww:Pv4m6pxQY3c:7XNnTPAzhY8&sa=N&cd=1&ct=rc&cs_p=svn://source.pentaho.org/svnroot/pentaho-reportdesigner/trunk&cs_f=src/org/pentaho/reportdesigner/lib/client/util/SingleApplicationLock.java#a0

SingleApplicationLock.java
有个实现了。
9 楼 Tanner 2006-12-08  
http://www.google.com/codesearch?hl=en&q=+lang:java+file+Open+full+occupied+show:07RRbObTL2o:DFnXDE8Qjvw:dkG5HqoyK5Y&sa=N&cd=8&ct=rc&cs_p=:pserver:anonymous%40hsqldb.cvs.sourceforge.net:/cvsroot/hsqldb+hsqldb-dev3&cs_f=src/org/hsqldb/persist/NIOLockFile.java#a0
似乎有关,接着试
8 楼 Tanner 2006-12-08  
Eclipse启动似乎是通过文件来判断的,
还有Java Logging API也会产生一个.lck文件,
可不可以通过标志文件的存取权限来判断?
试试看去,10分钟后回来。
7 楼 iceboundrock 2006-12-07  
C/C++里最常规的解决方案还是用Mutex,不过这个咚咚不知道在java里有没有。
6 楼 wdmsyf 2006-12-07  
引用

Nirvana     1 小时前
我觉得利用文件来保存启动标记也可以,只是思路变换一下,打开后一个实例,强制关闭前一个:
启动实例时生成唯一标记,保存变量,写入文件,然后循环读取文件内容,判断是否与保存变量相等,不等则退出。
第二实例运行时因操作同一文件,必然会满足第一实例不等的条件,迫使第一实例关闭。

刚才做了个小实验,发现也可行。

我个人觉得Nirvana的方法不是很可行,你说在第二实例启动时,强制关闭第一实例,这存在两个问题:
1、如何做到从第二实例中关闭第一实例?
2、即使可以关闭第一实例,那第一实例中正在处理的数据咋办?而且这样强制关闭第一实例,感觉有点象病毒噢,呵呵!
5 楼 Nirvana 2006-12-07  
我觉得利用文件来保存启动标记也可以,只是思路变换一下,打开后一个实例,强制关闭前一个:
启动实例时生成唯一标记,保存变量,写入文件,然后循环读取文件内容,判断是否与保存变量相等,不等则退出。
第二实例运行时因操作同一文件,必然会满足第一实例不等的条件,迫使第一实例关闭。

刚才做了个小实验,发现也可行。
4 楼 wdmsyf 2006-12-07  
引用

complystill     20 分钟前
确实是很不错的一个方法, 绝大部分服务器系统上, 端口相对来说还是比较cheap的.

这个问题经典的跨平台解决方法是建一个命名的系统互斥量, 它的生命周期也是跟着进程的. 不过Java平台不倾向于提供直接操作宿主系统资源的途径, 自己也是以虚拟机为全部逻辑环境, 不提供宿主系统范围的Inter-JVM-Communication机制. 用端口绑定方式来实现互斥确实有点另类, 不过对于纯Java应用来说不失为最佳的解决方案.

另可以有一点改进的地方, 就是绑定到 InetAddress.getLocalHost() 这个本机地址(在大多数OS上相当于127.0.0.1), 这样进一步不会占用服务器外部ip上的端口. 不过一般的服务器上服务进程通常也不指定具体外部地址, 而是绑定到所有本机地址的端口, 即便这样改过以后还是会影响他们的启动. 所以这个改进除非在很大型的系统上, 服务应用各自指定具体的外部地址去绑定时才有作用.

谢谢各位的回复,特别是complystill和Sunteya 给我开扩了思路。
我得到的回报比我给出的多得多啊!!

相关推荐

    java程序自动重新启动

    4. **程序启动和退出**:在Java中,可以通过`System.exit()`方法来终止程序,但重新启动程序需要借助操作系统API(如通过Runtime.getRuntime().exec())或使用外部脚本。 5. **设计模式**:AbstractRun.java可能...

    java应用程序开机自动启动

    Java应用程序开机自动启动是系统管理中的一个重要概念,它允许开发者创建的服务在操作系统启动时自动运行,无需用户手动干预。这在服务器维护、后台任务执行、持续监控等场景中非常常见。下面将详细介绍如何实现Java...

    java 实现文件MD5 加密比较,防止上传重复文件

    这个类可以实现,java语言下通过对上传文件进行MD5加密,然后将加密值与原来上传的文件加密值进行对比,防止上传重复文件。

    java实现防止表单重复提交

    服务器端避免表单的重复提交,利用同步令牌来解决重复提交的基本原理如下:(1)用户访问提交数据的页面,服务器端在这次会话中,创建一个session对象,并产生一个令牌值,将这个令牌值作为隐藏输入域的值,随表单一起发送到...

    Java 程序设计与项目实践

    1. **简单计算器**:构建一个能进行基本算术运算的GUI应用程序。 2. **学生管理系统**:设计一个包含学生信息录入、查询、修改和删除功能的系统。 3. **网上书店**:模拟一个简单的在线购书平台,涉及用户注册、...

    JAVA语言程序设计(郎波主编)例题

    异常处理是JAVA程序设计中的另一个重要概念,用于处理运行时错误。书中可能有涉及try-catch-finally语句和自定义异常的例题,帮助学习者理解如何优雅地处理程序可能出现的问题,提高程序的健壮性。 最后,文件I/O和...

    Java后台防止客户端重复请求、提交表单实现原理

    Java后台防止客户端重复请求、提交表单实现原理 ...Java后台防止客户端重复请求、提交表单实现原理是一个重要的解决方案,用于防止用户重复提交表单或请求,避免因重复提交而导致的数据错乱或其他问题。

    自考java 04747《Java语言程序设计(一)》教材电子版

    `表示声明了一个整型变量age并赋值为25。 2. **数据类型**:Java有两大类数据类型:基本数据类型(如int、char、boolean)和引用数据类型(如类、接口、数组)。理解不同类型的特点和使用场景是编写有效Java代码的...

    java 求爱 小程序

    LoveForEver.java很可能包含了一个主类,这个类包含了整个求爱小程序的启动点和主要功能。在Java中,通过`public static void main(String[] args)`方法来定义程序的入口点。 【love.exe】是Windows操作系统下的可...

    在Win7系统下抛出java.net.SocketException的解决办法

    在使用MyEclipse开发工具结合JBoss服务器进行项目开发的过程中,遇到一个常见但令人头疼的问题:当启动JBoss服务器时,控制台窗口频繁出现一系列警告信息,包括: - **SocketException**:“Unrecognized Windows ...

    Java Web程序设计教程.pdf

    标题《Java Web程序设计教程》与描述《Java Web程序设计教程 Java Web程序设计教程》中的知识点主要涵盖了Java Web应用开发领域的核心技术与实践。本书作为21世纪高等学校计算机规划教材,由范立锋与林果园共同编著...

    windows 系统下启动与结束java的jar包的脚本(包括如何设置进程名称)

    为了方便地启动Java应用程序(通常是以`.jar`文件的形式存在),我们可以创建一个批处理文件(`.bat`文件)。这个文件会调用Java运行时环境(JRE或JDK)来执行指定的`.jar`文件。此外,我们还将介绍如何通过参数设置...

    微信小程序+Java后台完整代码

    在“微信小程序+Java后台完整代码”中,我们可以看到一个完整的电商解决方案,包括前端的小程序部分和后端的Java服务。小程序商城的部分主要负责展示商品、处理用户交互、实现购物车功能、订单管理以及支付流程等。...

    java源码包---java 源码 大量 实例

     Tcp服务端与客户端的JAVA实例源代码,一个简单的Java TCP服务器端程序,别外还有一个客户端的程序,两者互相配合可以开发出超多的网络程序,这是最基础的部分。 递归遍历矩阵 1个目标文件,简单! 多人聊天室 3...

    InstallShield 制作安装程序(java web程序)

    本文主要探讨如何使用InstallShield工具来创建一个针对Java Web程序的.exe安装程序,该程序不仅包含了JDK(Java Development Kit),还集成了Resin服务器。 InstallShield是一款功能强大的安装制作软件,它允许...

    java程序设计 TCP文件传输程序 JFileChooser实现文件选择 可以设置路径

    Java程序设计中,TCP文件传输程序是一个常见的任务,它涉及到网络编程、IO流处理以及用户交互界面的设计。在这个项目中,我们使用了Java的核心库来实现客户端与服务器之间的文件交换,利用TCP(传输控制协议)作为...

    JAVA入门程序例子

    这个名为"JAVA入门程序例子"的压缩包提供了初学者学习Java语言的良好资源,涵盖了基础语法、J2SE常用包的运用、Java Swing图形界面设计以及JDBC数据库连接技术。 1. **JAVA语法基础**: - **变量与数据类型**:...

    Java程序启动器 Java program launcher.exe

    解决双击 jar 和 class文件不能直接在windows命令提示符下运行的问题,不用手动修改注册表和环境变量,把文件的打开方式设为本程序即可

    java 聊天程序 代码

    在本压缩包中,包含的文件很可能是“一个最简单的聊天程序”,这可能是一个基础的Java项目,用于教学或实践目的。下面将详细讨论Java聊天程序的关键知识点。 1. **Socket编程**:Java聊天程序的基础是网络编程中的...

Global site tag (gtag.js) - Google Analytics