Prevent launching multiple instances of a java application
Asked Answered
A

13

32

I want to prevent the user from running my java application multiple times in parallel.

To prevent this, I have created a lock file when am opening the application, and delete the lock file when closing the application.

When the application is running, you can not open an another instance of jar. However, if you kill the application through task manager, the window closing event in the application is not triggered and the lock file is not deleted.

How can I make sure the lock file method works or what other mechanism could I use?

Alister answered 12/8, 2011 at 5:54 Comment(0)
S
15

You could use a FileLock, this also works in environments where multiple users share ports:

String userHome = System.getProperty("user.home");
File file = new File(userHome, "my.lock");
try {
    FileChannel fc = FileChannel.open(file.toPath(),
            StandardOpenOption.CREATE,
            StandardOpenOption.WRITE);
    FileLock lock = fc.tryLock();
    if (lock == null) {
        System.out.println("another instance is running");
    }
} catch (IOException e) {
    throw new Error(e);
}

Also survives Garbage Collection. The lock is released once your process ends, doesn't matter if regular exit or crash or whatever.

Siccative answered 10/10, 2017 at 14:13 Comment(3)
Note that this leaves behind the lock file. If you want it deleted when you exit, you might call file.deleteOnExit() which will work if the process exits normally.Kusin
In my case, the lock got garbage collected when I declared it inside a method. I then moved it to an instance variable, and now it works like a charm.Blackout
I'm using FileLock and FileChannel instances exclusively as local vars inside a method, just tried with GC triggered by jvisualvm, the second launch of my app still saw the lock of the first launch. @CyB3RC0nN0R Would you mind to elaborate and show your code?Siccative
L
13

Similar discussion is at http://www.daniweb.com/software-development/java/threads/83331

Bind a ServerSocket. If it fails to bind then abort the startup. Since a ServerSocket can be bound only once, only single instsances of the program will be able to run.

And before you ask, no. Just because you bind a ServerSocket, does not mean you are open to network traffic. That only comes into effect once the program starts "listening" to the port with accept().

Lint answered 12/8, 2011 at 5:58 Comment(2)
PSA: A server socket works great, just don't forget to assign it to something that won't get garbage collected over the life of the process or the exclusion benefit will be short lived (or worse, it will work just long enough to make you wonder what happened when it stops).Quotidian
What happens if more than one user try to run the application? Won't they conflict on opening the socket?Claviform
E
6

I see two options you can try:

  1. Use a Java shutdown hook
  2. Have your lock file hold the main process number. The process should exist when you lanuch another instance. If it's not found in your system, you can assume that the lock can be dismissed and overwritten.
Eyebright answered 12/8, 2011 at 6:0 Comment(1)
Except when windows taskmanager kills it or Linux kill-9 then your SOLPriorate
A
5

Creating a server socket, bounds to a specific port with a ServerSocket instance as the application starts is a straight way.
Note that ServerSocket.accept() blocks, so running it in its own thread makes sense to not block the main Thread.

Here is an example with a exception thrown as detected :

public static void main(String[] args) {       
    assertNoOtherInstanceRunning();
    ...     // application code then        
}

public static void assertNoOtherInstanceRunning() {       
    new Thread(() -> {
        try {
            new ServerSocket(9000).accept();
        } catch (IOException e) {
          throw new RuntimeException("the application is probably already started", e);
        }
    }).start();       
}
Achromaticity answered 5/4, 2018 at 15:9 Comment(0)
E
3

You could write the process id of the process that created the lock file into the file. When you encounter an existing lock file, you do not just quit, but you check if the process with that id is still alive. If not, you can go ahead.

Econah answered 12/8, 2011 at 5:59 Comment(3)
Great idea! +1. But this idea, keeping a file to check whether an app is running, feels like a bit ugly. Isn't it. Aren't there any gracefully methods at all ??? I don't think that this's the method that other popular apps are using. I'm curious..Outgoing
You should also check if the process is the right one (imagine crashing, restarting the machine. But then, we could just walk all process anyway and could get rid of the ugly file check. (Using sockets myself, which allows to forward arguments).Abhenry
At least on Windows this is not a good idea because a process ID can be recycled. You could thus potentially get a false positive. See #26301882Latashialatch
V
1

You can create a Server socket like

       new ServerSocket(65535, 1, InetAddress.getLocalHost());

at very beginning of your code. Then if AddressAlreadyInUse exception caught in main block you can display the appropriate message.

Venenose answered 14/1, 2014 at 6:45 Comment(1)
If another application use 65535 for sending data, you will not bind to this port, even no instance is running.Shepp
S
1

There are already available java methods in File class to achieve the same. The method is deleteOnExit() which ensure the file is automatically deleted when the JVM exits. However, it does not cater to forcible terminations. One should use FileLock in case of forcible termination.

For more details check, https://docs.oracle.com/javase/7/docs/api/java/io/File.html

Thus code snippet which could be used in the main method can be like :

public static void main(String args[]) throws Exception {

    File f = new File("checkFile");

    if (!f.exists()) {
        f.createNewFile();
    } else {
        System.out.println("App already running" );
        return;
    }

    f.deleteOnExit();

    // whatever your app is supposed to do
    System.out.println("Blah Blah")
}
Spiroid answered 12/10, 2017 at 9:6 Comment(1)
Please have a look at my answer: https://mcmap.net/q/219937/-prevent-launching-multiple-instances-of-a-java-application I have used your answer and improved upon it a bit. I have also given you guys credit.Fowkes
M
0

..what other mechanism could I use?

If the app. has a GUI it can be launched using Java Web Start. The JNLP API provided to web-start offers the SingleInstanceService. Here is my demo. of the SingleInstanceService.

Marchpast answered 12/8, 2011 at 9:14 Comment(0)
L
0

You can write something like this.

If file exists try to delete it. if it is not able to delete. We can say that application is already running.

Now create the same file again and redirect the sysout and syserr.

This works for me

Longcloth answered 25/12, 2015 at 10:56 Comment(0)
F
0

Simple lock and advanced lock

I developed 2 solutions for this problem. I was also looking for an easy way of doing this without using any libraries and a lot of code.

My solutions are based on: https://mcmap.net/q/219937/-prevent-launching-multiple-instances-of-a-java-application which I have improved upon. Therefore I would like to thank @akshaya pandey and @rbento

Simple file lock

package YOUR_PACKAGE_NAME;

import java.io.File;
import java.io.IOException;

/**
 * Minimal reproducible example (MRE) - Example of a simple lock file.
 * @author Remzi Cavdar - [email protected] - <a href="https://github.com/Remzi1993">@Remzi1993</a>
 */
public class Main {
    public static void main(String[] args) {
        /*
         * Prevents the user of starting multiple instances of the application.
         * This is done by creating a temporary file in the app directory.
         * The temp file should be excluded from git and is called App.lock in this example.
         */
        final File FILE = new File("App.lock");

        try {
            if (FILE.createNewFile()) {
                System.out.println("Starting application");
            } else {
                System.err.println("The application is already running!");
                return;
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        /*
         * Register a shutdown hook to delete the lock file when the application is closed. Even when forcefully closed
         * with the task manager. (Tested on Windows 11 with JavaFX 19)
         */
        FILE.deleteOnExit();
        // Whatever your app is supposed to do
    }
}

Advanced lock

package YOUR_PACKAGE_NAME;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;

/**
 * Minimal reproducible example (MRE) - Example of a more advanced lock system.
 * @author Remzi Cavdar - [email protected] - <a href="https://github.com/Remzi1993">@Remzi1993</a>
 */
public class Main {
    public static void main(String[] args) {
        /*
         * Prevents the user of starting multiple instances of the application.
         * This is done by creating a temporary file in the app directory.
         * The temp file should be excluded from git and is called App.lock in this example.
         */
        final File FILE = new File("App.lock");

        if (FILE.exists()) {
            System.err.println("The application is already running!");
            return;
        }

        try (
                FileOutputStream fileOutputStream = new FileOutputStream(FILE);
                FileChannel channel = fileOutputStream.getChannel();
                FileLock lock = channel.lock()
        ) {
            System.out.println("Starting application");
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        /*
         * Register a shutdown hook to delete the lock file when the application is closed. Even when forcefully closed
         * with the task manager. (Tested on Windows 11 with JavaFX 19)
         */
        FILE.deleteOnExit();
        // Whatever your app is supposed to do
    }
}

Testing

  • Tested on: 31-10-2022
  • Tested OS: Windows 11 - Version 21H2 (OS Build 22000.1098)
  • Tested with: OpenJDK 19 - Eclipse Temurin JDK with Hotspot 19+36(x64)

I closed the application and also forcefully closed the application with task manager on Windows both times the lock file seems to be deleted upon (force) close.

Fowkes answered 30/10, 2022 at 23:30 Comment(0)
V
-1

I struggled with this same problem for a while... none of the ideas presented here worked for me. In all cases, the lock (file, socket or otherwise) did not persist into the 2nd process instance, so the 2nd instance still ran.

So I decided to try an old school approach to simply crate a .pid file with the process id of the first process. Then any 2nd process would quit if it finds the .pid file, and also the process number specified in the file is confirmed to be still running. This approach worked for me.

There is a fair bit of code, which I provide here in full for your use... a complete solution.

package common.environment;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.*;
import java.nio.charset.Charset;

public class SingleAppInstance
{
    private static final @Nonnull Logger log = LogManager.getLogger(SingleAppInstance.class.getName());

    /**
     * Enforces that only a single instance of the given component is running. This
     * is resilient to crashes, unexpected reboots and other forceful termination
     * scenarios.
     *
     * @param componentName = Name of this component, for disambiguation with other
     *   components that may run simultaneously with this one.
     * @return = true if the program is the only instance and is allowed to run.
     */
    public static boolean isOnlyInstanceOf(@Nonnull String componentName)
    {
        boolean result = false;

        // Make sure the directory exists
        String dirPath = getHomePath();
        try
        {
            FileUtil.createDirectories(dirPath);
        }
        catch (IOException e)
        {
            throw new RuntimeException(String.format("Unable to create directory: [%s]", dirPath));
        }

        File pidFile = new File(dirPath, componentName + ".pid");

        // Try to read a prior, existing pid from the pid file. Returns null if the file doesn't exist.
        String oldPid = FileUtil.readFile(pidFile);

        // See if such a process is running.
        if (oldPid != null && ProcessChecker.isStillAllive(oldPid))
        {
            log.error(String.format("An instance of %s is already running", componentName));
        }
        // If that process isn't running, create a new lock file for the current process.
        else
        {
            // Write current pid to the file.
            long thisPid = ProcessHandle.current().pid();
            FileUtil.createFile(pidFile.getAbsolutePath(), String.valueOf(thisPid));

            // Try to be tidy. Note: This won't happen on exit if forcibly terminated, so we don't depend on it.
            pidFile.deleteOnExit();

            result = true;
        }

        return result;
    }

    public static @Nonnull String getHomePath()
    {
        // Returns a path like C:/Users/Person/
        return System.getProperty("user.home") + "/";
    }
}

class ProcessChecker
{
    private static final @Nonnull Logger log = LogManager.getLogger(io.cpucoin.core.platform.ProcessChecker.class.getName());

    static boolean isStillAllive(@Nonnull String pidStr)
    {
        String OS = System.getProperty("os.name").toLowerCase();
        String command;
        if (OS.contains("win"))
        {
            log.debug("Check alive Windows mode. Pid: [{}]", pidStr);
            command = "cmd /c tasklist /FI \"PID eq " + pidStr + "\"";
        }
        else if (OS.contains("nix") || OS.contains("nux"))
        {
            log.debug("Check alive Linux/Unix mode. Pid: [{}]", pidStr);
            command = "ps -p " + pidStr;
        }
        else
        {
            log.warn("Unsupported OS: Check alive for Pid: [{}] return false", pidStr);
            return false;
        }
        return isProcessIdRunning(pidStr, command); // call generic implementation
    }

    private static boolean isProcessIdRunning(@Nonnull String pid, @Nonnull String command)
    {
        log.debug("Command [{}]", command);
        try
        {
            Runtime rt = Runtime.getRuntime();
            Process pr = rt.exec(command);

            InputStreamReader isReader = new InputStreamReader(pr.getInputStream());
            BufferedReader bReader = new BufferedReader(isReader);
            String strLine;
            while ((strLine = bReader.readLine()) != null)
            {
                if (strLine.contains(" " + pid + " "))
                {
                    return true;
                }
            }

            return false;
        }
        catch (Exception ex)
        {
            log.warn("Got exception using system command [{}].", command, ex);
            return true;
        }
    }
}

class FileUtil
{
    static void createDirectories(@Nonnull String dirPath) throws IOException
    {
        File dir = new File(dirPath);
        if (dir.mkdirs())   /* If false, directories already exist so nothing to do. */
        {
            if (!dir.exists())
            {
                throw new IOException(String.format("Failed to create directory (access permissions problem?): [%s]", dirPath));
            }
        }
    }

    static void createFile(@Nonnull String fullPathToFile, @Nonnull String contents)
    {
        try (PrintWriter writer = new PrintWriter(fullPathToFile, Charset.defaultCharset()))
        {
            writer.print(contents);
        }
        catch (IOException e)
        {
            throw new RuntimeException(String.format("Unable to create file at %s! %s", fullPathToFile, e.getMessage()), e);
        }
    }

    static @Nullable String readFile(@Nonnull File file)
    {
        try
        {
            try (BufferedReader fileReader = new BufferedReader(new FileReader(file)))
            {
                StringBuilder result = new StringBuilder();

                String line;
                while ((line = fileReader.readLine()) != null)
                {
                    result.append(line);
                    if (fileReader.ready())
                        result.append("\n");
                }
                return result.toString();
            }
        }
        catch (IOException e)
        {
            return null;
        }
    }
}

To use it, simply invoke it like this:

if (!SingleAppInstance.isOnlyInstanceOf("my-component"))
{
    // quit
}

I hope you find this helpful.

Veritable answered 16/10, 2019 at 22:6 Comment(1)
This design has a minor but real downside: If the app quits unexpectedly, the pid file won't be deleted. There exists the possibility that on a subsequent run that some other running process may be running that by coincidence has the same pid as in the pid file, and the program will not run. You'd have to delete the pid file first.Veritable
S
-1

Finally I found really simple library to achieve this. You can you use JUniqe.

The JUnique library can be used to prevent a user to run at the same time more instances of the same Java application.

This is an example how to use it from the documentation

public static void main(String[] args) {
    String appId = "myapplicationid";
    boolean alreadyRunning;
    try {
        JUnique.acquireLock(appId);
        alreadyRunning = false;
    } catch (AlreadyLockedException e) {
        alreadyRunning = true;
    }
    if (!alreadyRunning) {
        // Start sequence here
    }
}
Soporific answered 16/1, 2021 at 8:1 Comment(0)
T
-1

here is a pretty rudimental approach. If your application is launched from a script, check the running java/javaw processes and their command line before launch

In windows

REM check if there is a javaw process running your.main.class
REM if found, go to the end of the script and skip the launch of a new instance

WMIC path win32_process WHERE "Name='javaw.exe'" get CommandLine 2>nul | findstr your.main.class >nul 2>&1
if %ERRORLEVEL% EQU 0 goto:eof
javaw your.main.class
Thermograph answered 30/6, 2022 at 7:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.