Jan 2, 2015

Howto run only single Java application instance

Sometimes in software development you need to run only one application instance. If you want to do this in Java you will have to think about realization, because this feature is not implement in the standard Java language library. I've found 2 basic approaches to make it work. They are based on port lock and file lock concepts.

My code is based on the file lock idea, because I think that port lock usage is not really reliable because of possible conflict in ports usage. I was trying to create really reusable piece of code :) which can handle cross application and cross platrom lock.

Note, that all code listed below was refactored and moved into my GitHub repository https://github.com/jneat/jneat
Updated features are described in this article Synchronize code over multiple JVM instances.

Don't know why, but many developers suggested to put lock file in the same directory with executable file. In this case you still be able to run another copy of application from another folder where your program was copied. Also there is possibility that your application will not have write access to the directory where it placed.

I solved this problems in next way. Lock file will be created in temporary system folder. File name will be generated as md5 hash, based on some application unique keyword. Another application instances will try to search for the appropriate lock file, and than decide what to do.

This is how my lock class usage sample:
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
}

AppLock class has only 2 static methods: setLock and ReleaseLock. Hope you will not have any problems in it usage :). This is cross platform code, it works on Windows and Linux..
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;

/**
 * The Class AppLock.
 *
 * @url http://nerdydevel.blogspot.com/2012/07/run-only-single-java-application-instance.html
 * @author rumatoest
 */
public class AppLock {

        /**
         * Instantiates a new app lock.
         */
        private AppLock() {
        }

        /** The lock_file. */
        File lock_file = null;

        /** The lock. */
        FileLock lock = null;

        /** The lock_channel. */
        FileChannel lock_channel = null;

        /** The lock_stream. */
        FileOutputStream lock_stream = null;

        /**
         * Instantiates a new app lock.
         *
         * @param key Unique application key
         * @throws Exception The exception
         */
        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);
                        // Hash string has no leading zeros
                        // Adding zeros to the beginnig of has string
                        while (hash_text.length() < 32) {
                                hash_text = "0" + hash_text;
                        }
                        lock_file = new File(tmp_dir + hash_text + ".app_lock");
                } catch (Exception ex) {
                        System.out.println("AppLock.AppLock() file fail");
                }

                // 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");
                }
        }

        /**
         * Release Lock.
         * Now another application instance can gain lock.
         *
         * @throws Throwable
         */
        private void release() throws Throwable {
                if (lock.isValid()) {
                        lock.release();
                }
                if (lock_stream != null) {
                        lock_stream.close();
                }
                if (lock_channel.isOpen()) {
                        lock_channel.close();
                }
                if (lock_file.exists()) {
                        lock_file.delete();
                }
        }

        @Override
        protected void finalize() throws Throwable {
                this.release();
                super.finalize();
        }

        /** The instance. */
        private static AppLock instance;

        /**
         * Set application lock.
         * Method can be run only one time per application.
         * All next calls will be ignored.
         *
         * @param key Unique application lock key
         * @return true, if successful
         */
        public static boolean setLock(String key) {
                if (instance != null) {
                        return true;
                }

                try {
                        instance = new AppLock(key);
                } catch (Exception ex) {
                        instance = null;
                        Logger.getLogger(AppLock.class.getName()).log(Level.SEVERE, "Fail to set AppLoc", ex);
                        return false;
                }

                Runtime.getRuntime().addShutdownHook(new Thread() {
                        @Override
                        public void run() {
                                AppLock.releaseLock();
                        }
                });
                return true;
        }

        /**
         * Trying to release Lock.
         * After release you can not user AppLock again in this application.
         */
        public static void releaseLock() {
                try {
                    if (instance == null) {
                            throw new NoSuchFieldException("INSTATCE IS NULL");
                    }
                    instance.release();
                } catch (Throwable ex) {
                        Logger.getLogger(AppLock.class.getName()).log(Level.SEVERE, "Fail to release", ex);
                }
        }
}

Possible improvements

So we are creating an empty file in AppLock, but this file can be not empty. You can serialize any data there, and than another app instance will start, it can read and unserialize any data from the lock file. You can use this feature to pass the first application instance process id or random port nuber aquired by the first app instance.