Search code examples
java

Prevent launching multiple instances of a java application by locking a file is not working


For some reason this solution to prevent multiple instances of the app from running doesn't work anymore - what's wrong?

I expect the second launch of this app will print: "another instance is running" and will stop with the error, but it doesn't happen. The second instance works the same as the first.

import java.io.File;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.StandardOpenOption;

public class JustOneTest {

    public static void main(String[] args) {
        runCheck();
        while (true) {
            try {
                System.out.print(".");
                Thread.sleep(5 * 60);
            } catch (Exception e) {
                    e.printStackTrace();
            }
        }
    }
    
    private static void runCheck() {
        System.out.println("start check: is app running");
        final File file = new File(System.getProperty("user.home"), "khf.lock");

        try {
            FileLock lock;
            
            try (FileChannel fc = FileChannel.open(file.toPath(), 
                    StandardOpenOption.CREATE,
                    StandardOpenOption.WRITE)) {
                lock = fc.tryLock();
            }

            if (lock == null) {
                System.out.println("another instance is running");
                throw new Error("another instance is running");
            }

            System.out.println(lock);
            // output: sun.nio.ch.FileLockImpl[0:9223372036854775807 exclusive invalid]
            
        } catch (IOException e) {
            throw new Error(e);
        }
        
        System.out.println("start check completed");
    }
  
}

Solution

  • Your created FileLock instance get invalidated after your try block because you close the FileChannel, which was used to get the FileLock instance. This is specified in the java.nio.channels.FileLock documentation:

    A file-lock object is initially valid. It remains valid until the lock is released by invoking the release method, by closing the channel that was used to acquire it, or by the termination of the Java virtual machine, whichever comes first.

    (bold emphasis mine)

    So when you close the FileChannel in your try block, you also close the FileLock instance, which releases the lock.

    To prevent releasing the lock after you have acquired it, you have to keep the FileChannel instance "alive" and not close it (same for the FileLock instance). This means you might want to put the FileChannel instance in a static field and "keep it there". See the following adjustment in your code:

    import java.io.File;
    import java.io.IOException;
    import java.nio.channels.FileChannel;
    import java.nio.channels.FileLock;
    import java.nio.file.StandardOpenOption;
    
    public class JustOneTest {
    
        public static FileLock lock;
        public static FileChannel fc;
        public static void main(String[] args) {
            runCheck();
            while (true) {
                try {
                    System.out.print(".");
                    Thread.sleep(5 * 60);
                } catch (Exception e) {
                        e.printStackTrace();
                }
            }
        }
        
        private static void runCheck() {
            System.out.println("start check: is app running");
            final File file = new File(System.getProperty("user.home"), "khf.lock");
    
            try {
                
                fc = FileChannel.open(file.toPath(), 
                        StandardOpenOption.CREATE,
                        StandardOpenOption.WRITE);
                lock = fc.tryLock();
                
                System.out.println(lock);
    
                if (lock == null) {
                    System.out.println("another instance is running");
                    throw new Error("another instance is running");
                }
                
            } catch (IOException e) {
                throw new Error(e);
            }
            
            System.out.println("start check completed");
            System.out.println(lock);
            System.out.println(lock.isValid());
        }
      
    }
    

    This fill generate the following example output in the first java execution instance:

    start check: is app running
    sun.nio.ch.FileLockImpl[0:9223372036854775807 exclusive valid]
    start check completed
    sun.nio.ch.FileLockImpl[0:9223372036854775807 exclusive valid]
    true
    .........
    

    This is the output of the second java execution instance, while the first one is running:

    start check: is app running
    null
    another instance is running
    Exception in thread "main" java.lang.Error: another instance is running
            at JustOneTest.runCheck(JustOneTest.java:38)
            at JustOneTest.main(JustOneTest.java:12)