Implement a single instance JAVA application

1. Use file lock to ensure single instance is running for this application.
 private static boolean lockInstance(final String lockFile) {  
   try {  
     final File file = new File(lockFile);  
     final RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");  
     final FileLock fileLock = randomAccessFile.getChannel().tryLock();  
     if (fileLock != null) {  
       Runtime.getRuntime().addShutdownHook(new Thread() {  
         public void run() {  
           try {  
             fileLock.release();  
             randomAccessFile.close();  
             file.delete();  
           } catch (Exception e) {  
             log.error("Unable to remove lock file: " + lockFile, e);  
           }  
         }  
       });  
       return true;  
     }  
   } catch (Exception e) {  
     log.error("Unable to create and/or lock file: " + lockFile, e);  
   }  
   return false;  
 }     

2. Another file locking example:
2a.JustOneLock.java
 import java.io.*;  
 import java.nio.channels.*;  
   
 public class JustOneLock {  
   private String appName;  
   private File file;  
   private FileChannel channel;  
   private FileLock lock;  
   
   public JustOneLock(String appName) {  
     this.appName = appName;  
   }  
   
   public boolean isAppActive() {  
     try {  
       file = new File  
          (System.getProperty("user.home"), appName + ".tmp");  
       channel = new RandomAccessFile(file, "rw").getChannel();  
   
       try {  
         lock = channel.tryLock();  
       }  
       catch (OverlappingFileLockException e) {  
         // already locked  
         closeLock();  
         return true;  
       }  
   
       if (lock == null) {  
         closeLock();  
         return true;  
       }  
   
       Runtime.getRuntime().addShutdownHook(new Thread() {  
           // destroy the lock when the JVM is closing  
           public void run() {  
             closeLock();  
             deleteFile();  
           }  
         });  
       return false;  
     }  
     catch (Exception e) {  
       closeLock();  
       return true;  
     }  
   }  
   
   private void closeLock() {  
     try { lock.release(); }  
     catch (Exception e) { }  
     try { channel.close(); }  
     catch (Exception e) { }  
   }  
   
   private void deleteFile() {  
     try { file.delete(); }  
     catch (Exception e) { }  
   }  
 }  

2b. JustOneTest.java
 public class JustOneTest {  
   public static void main(String[] args) {  
     new JustOneTest().test();  
   }  
   
   void test() {  
     JustOneLock ua = new JustOneLock("JustOneId");  
   
     if (ua.isAppActive()) {  
       System.out.println("Already active.");  
       System.exit(1);    
     }  
     else {  
       System.out.println("NOT already active.");  
       try {  
         while(true) {  
            try { System.out.print("."); Thread.sleep(5 * 60); }  
            catch(Exception e) { e.printStackTrace(); }  
         }  
        }  
       catch (Exception e) { }  
     }  
   }  
 }  

3. Improved File locking to solve: People can still run another copy of application from another folder where your program was copied.
3a. Application use AppLock class
 try {  
   // Try to get LOCK //  
   if (!AppLock.setLock("MY_CUSTOM_LOCK_KEY")) {  
     throw new RuntimeException("Only one application instance may run at the same time!");  
   }  
   
   // YOUR CODE  
 }  
 finally{  
   AppLock.releaseLock(); // Release lock  
 }  

3b. AppLock class
 import java.io.File;  
 import java.io.FileOutputStream;  
 import java.nio.channels.FileChannel;  
 import java.nio.channels.FileLock;  
 import java.util.logging.Level;  
 import java.util.logging.Logger;  
   
 /**  
  * AppLock  
  * @author http://fresh2L.com  
  * @version 1.0  
  */  
 public class AppLock  
 {  
   
  /** hidden constructor */  
  private AppLock() { }  
   
  /** Lock file */  
  File lock_file = null;  
   
  FileLock lock = null;  
   
  FileChannel lock_channel = null;  
   
  FileOutputStream lock_stream = null;  
   
  /**  
   * Creates class lock instance  
   * @param key Unique application key  
   */  
  private AppLock(String key) throws Exception  
  {  
   String tmp_dir = System.getProperty("java.io.tmpdir");  
   if (!tmp_dir.endsWith(System.getProperty("file.separator"))) {  
    tmp_dir += System.getProperty("file.separator");  
   }  
     
   // Acquire MD5  
   try {  
    java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");  
    md.reset();  
    String hash_text = new java.math.BigInteger(1, md.digest(key.getBytes())).toString(16);  
   
    while (hash_text.length() < 32) {  
     hash_text = "0" + hash_text;  
    }  
    lock_file = new File(tmp_dir + hash_text + ".app_lock");  
   }  
   catch (Exception ex) {  
   }  
   
   // MD5 acquire fail  
   if (lock_file == null) {  
     lock_file = new File(tmp_dir + key + ".app_lock");  
   }  
   
   lock_stream = new FileOutputStream(lock_file);  
   
   String f_content = "Java AppLock Object\r\nLocked by key: " + key + "\r\n";  
   lock_stream.write(f_content.getBytes());  
   
   lock_channel = lock_stream.getChannel();  
   
   lock = lock_channel.tryLock();  
   
   if (lock == null) {  
    throw new Exception("Can't create Lock");  
   }  
  }  
   
  /**  
   * Remove Lock. Now another application instance can gain lock.  
   * @throws Throwable  
   */  
  private void release() throws Throwable  
  {  
   if (lock != null) {  
    lock.release();  
   }  
   
   if (lock_channel != null) {  
    lock_channel.close();  
   }  
   
   if (lock_stream != null) {  
    lock_stream.close();  
   }  
   
   if (lock_file != null) {  
    lock_file.delete();  
   }  
  }  
   
  @Override  
  protected void finalize() throws Throwable  
  {  
   this.release();  
   super.finalize();  
  }  
   
  private static AppLock instance;  
   
  /**  
   * Set Lock based on input key  
   * Method can be run only one time per application. Second calls will be ignored.  
   *  
   * @param key Lock key  
   */  
  public static boolean setLock(String key)  
  {  
   if (instance != null) {  
    return true;  
   }  
   
   try {  
    instance = new AppLock(key);  
   }  
   catch (Exception ex) {  
    instance = null;  
    Logger.getLogger(Application.class.getName()).log(Level.SEVERE, "Обламался AppLock", ex);  
    return false;  
   }  
   
   Runtime.getRuntime().addShutdownHook(new Thread()  
   {  
    @Override  
    public void run()  
    {  
     AppLock.releaseLock();  
    }  
   });  
   return true;  
  }  
   
  /**  
   * Try to release Lock.  
   * After releasing you can not user AppLock again in application.  
   */  
  public static void releaseLock()  
  {  
   if (instance == null) {  
    return;  
   }  
   try {  
    instance.release();  
   }  
   catch (Throwable ex) {  
    Logger.getLogger(AppLock.class.getName()).log(Level.SEVERE, null, ex);  
   }  
  }  
 }  

4. Socket technique
4a. Server
 import java.io.*;  
 import java.net.*;  
   
 public class JustOneServer extends Thread {  
   // you may need to customize this for your machine  
   public static final int PORT = 80 ;   
   
   ServerSocket serverSocket = null;  
   Socket clientSocket = null;  
   
   public void run() {  
   try {  
    // Create the server socket  
    serverSocket = new ServerSocket(port, 1);  
    while (true) {  
     // Wait for a connection  
     clientSocket = serverSocket.accept();  
     // System.out.println("*** Got a connection! ");  
     clientSocket.close();  
     }  
    }  
   catch (IOException ioe) {  
    System.out.println("Error in JustOneServer: " + ioe);  
    }  
   }  
  }  

4b. Client
 import java.io.*;  
 import java.net.*;  
   
 public class JustOne {  
  SimpleDummyServer sds = null;   
   
  public static void main(String args[]){  
   new JustOne().doit();  
   }  
   
  public void doit() {  
   try {  
    Socket clientSocket = new Socket("localhost", JustOneServer.PORT);  
    System.out.println("*** Already running!");  
    System.exit(1);  
    }  
   catch (Exception e) {  
    sds = new JustOneServer();  
    sds.start();  
    }  
     
   while(true) {  
    try { System.out.print("."); Thread.sleep(5 * 60); }  
    catch(Exception e) { e.printStackTrace(); }  
    }  
   }  
  }  

5. Another socket approach
5a. ApplicationInstanceManager class
 public class ApplicationInstanceManager {  
   
   private static ApplicationInstanceListener subListener;  
   
   /** Randomly chosen, but static, high socket number */  
   public static final int SINGLE_INSTANCE_NETWORK_SOCKET = 44331;  
   
   /** Must end with newline */  
   public static final String SINGLE_INSTANCE_SHARED_KEY = "$$NewInstance$$\n";  
   
   /**  
    * Registers this instance of the application.  
    *  
    * @return true if first instance, false if not.  
    */  
   public static boolean registerInstance() {  
     // returnValueOnError should be true if lenient (allows app to run on network error) or false if strict.  
     boolean returnValueOnError = true;  
     // try to open network socket  
     // if success, listen to socket for new instance message, return true  
     // if unable to open, connect to existing and send new instance message, return false  
     try {  
       final ServerSocket socket = new ServerSocket(SINGLE_INSTANCE_NETWORK_SOCKET, 10, InetAddress  
           .getLocalHost());  
       log.debug("Listening for application instances on socket " + SINGLE_INSTANCE_NETWORK_SOCKET);  
       Thread instanceListenerThread = new Thread(new Runnable() {  
         public void run() {  
           boolean socketClosed = false;  
           while (!socketClosed) {  
             if (socket.isClosed()) {  
               socketClosed = true;  
             } else {  
               try {  
                 Socket client = socket.accept();  
                 BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));  
                 String message = in.readLine();  
                 if (SINGLE_INSTANCE_SHARED_KEY.trim().equals(message.trim())) {  
                   log.debug("Shared key matched - new application instance found");  
                   fireNewInstance();  
                 }  
                 in.close();  
                 client.close();  
               } catch (IOException e) {  
                 socketClosed = true;  
               }  
             }  
           }  
         }  
       });  
       instanceListenerThread.start();  
       // listen  
     } catch (UnknownHostException e) {  
       log.error(e.getMessage(), e);  
       return returnValueOnError;  
     } catch (IOException e) {  
       log.debug("Port is already taken. Notifying first instance.");  
       try {  
         Socket clientSocket = new Socket(InetAddress.getLocalHost(), SINGLE_INSTANCE_NETWORK_SOCKET);  
         OutputStream out = clientSocket.getOutputStream();  
         out.write(SINGLE_INSTANCE_SHARED_KEY.getBytes());  
         out.close();  
         clientSocket.close();  
         log.debug("Successfully notified first instance.");  
         return false;  
       } catch (UnknownHostException e1) {  
         log.error(e.getMessage(), e);  
         return returnValueOnError;  
       } catch (IOException e1) {  
         log.error("Error connecting to local port for single instance notification");  
         log.error(e1.getMessage(), e1);  
         return returnValueOnError;  
       }  
   
     }  
     return true;  
   }  
   
   public static void setApplicationInstanceListener(ApplicationInstanceListener listener) {  
     subListener = listener;  
   }  
   
   private static void fireNewInstance() {  
    if (subListener != null) {  
     subListener.newInstanceCreated();  
    }  
  }  
 }  

5b. Listener Interface
 public interface ApplicationInstanceListener {  
   public void newInstanceCreated();  
 }  
   

5c. Main application
 public class MyApplication {  
   public static void main(String[] args) {  
     if (!ApplicationInstanceManager.registerInstance()) {  
           // instance already running.  
           System.out.println("Another instance of this application is already running. Exiting.");  
           System.exit(0);  
     }  
     ApplicationInstanceManager.setApplicationInstanceListener(new ApplicationInstanceListener() {  
      public void newInstanceCreated() {  
        System.out.println("New instance detected...");  
        // this is where your handler code goes...  
      }  
     });  
   }  
 }  

6. Use Launch4j to wrap the application
http://launch4j.sourceforge.net/

7. Use sun.jvmstat package
http://java.sun.com/performance/jvmstat/
7a. Main class
 // imports  
 import sun.management.ConnectorAddressLink;  
 import sun.jvmstat.monitor.HostIdentifier;  
 import sun.jvmstat.monitor.Monitor;  
 import sun.jvmstat.monitor.MonitoredHost;  
 import sun.jvmstat.monitor.MonitoredVm;  
 import sun.jvmstat.monitor.MonitoredVmUtil;  
 import sun.jvmstat.monitor.MonitorException;  
 import sun.jvmstat.monitor.VmIdentifier;  
   
  public static void main(String args[]) {  
  /* The method ManagementFactory.getRuntimeMXBean() returns an identifier with applcation PID  
  in the Sun JVM, but each jvm may have you own implementation.  
  So in anothers jvm, other than Sun, this code may not work., :(  
  */  
  RuntimeMXBean rt = ManagementFactory.getRuntimeMXBean();  
  final int runtimePid = Integer.parseInt(rt.getName().substring(0,rt.getName().indexOf("@")));  
   
  java.awt.EventQueue.invokeLater(new Runnable() {  
  public void run() {  
   
  // If exists another instance, show message and terminates the current instance.  
  // Otherwise starts application.  
  if (getMonitoredVMs(runtimePid))  
  {  
     new MainFrame().setVisible(true);  
  } else {  
     JOptionPane.showMessageDialog(null,"There is another instance of this application running.");  
  }  
  });  
  })  

7b. Method getMoniteredVMs
 private static boolean getMonitoredVMs(int processPid) {  
           MonitoredHost host;  
           Set vms;  
           try {  
                host = MonitoredHost.getMonitoredHost(new HostIdentifier(  
                          (String) null));  
                vms = host.activeVms();  
           } catch (java.net.URISyntaxException sx) {  
                throw new InternalError(sx.getMessage());  
           } catch (MonitorException mx) {  
                throw new InternalError(mx.getMessage());  
           }  
           MonitoredVm mvm = null;  
           String processName = null;  
           try {  
                mvm = host.getMonitoredVm(new VmIdentifier(String  
                          .valueOf(processPid)));  
                processName = MonitoredVmUtil.commandLine(mvm);  
                processName = processName.substring(  
                          processName.lastIndexOf("\\") + 1, processName.length());  
                mvm.detach();  
           } catch (Exception ex) {  
   
           }  
           // This line is just to verify the process name. It can be removed.  
           JOptionPane.showMessageDialog(null, processName);  
           for (Object vmid : vms) {  
                if (vmid instanceof Integer) {  
                     int pid = ((Integer) vmid).intValue();  
                     String name = vmid.toString(); // default to pid if name not  
                                                             // available  
                     try {  
                          mvm = host.getMonitoredVm(new VmIdentifier(name));  
                          // use the command line as the display name  
                          name = MonitoredVmUtil.commandLine(mvm);  
                          name = name.substring(name.lastIndexOf("\\") + 1,  
                                    name.length());  
                          mvm.detach();  
                          if ((name.equalsIgnoreCase(processName))  
                                    && (processPid != pid))  
                               return false;  
                     } catch (Exception x) {  
                          // ignore  
                     }  
                }  
           }  
   
           return true;  
      }  

Reference:
http://www.rgagnon.com/javadetails/java-0288.html
http://audiprimadhanty.wordpress.com/2008/06/30/ensuring-one-instance-of-application-running-at-one-time/ 
http://www.rbgrn.net/content/43-java-single-application-instance

留言

這個網誌中的熱門文章

Disable ionic's sidemenu content drag to toggle menu

Multiple writable mappings exist for the field. Only one may be defined as writable, all others must be specified read-only.

java.lang.NoClassDefFoundError: org/apache/xerces/jaxp/datatype/XMLGregorianCalendarImpl$Parser