Java Single Application Instance

| | Comments (1)
A single instance application is one that only allows for 1 of the application to run no matter how many times the user tries to launch.  Windows and linux native applications have an API to detect the instance of an application but when in Java, it is not quite as easy or reliable.  To complicate matters, often times the requirement will be for the original application instance to react in some way to the launch, as in bringing the window to the front, opening a file or displaying a message.

Fortunately it's not all that difficult to code this if you know the trick.  In this article I'll give you all the code you need to copy and paste into your application to make it single instance.
I've seen suggestions to use files as a cross-instance lock, but that is unreliable as application crashes and other IO errors may not allow for the file to be cleaned up.  The better solution in my opinion is to use a local server socket. 

This code example works by having the first instance attempt to open a listening socket on the localhost interface.  If it's able to open the socket, it is assumed that this is the first instance of the application to be launched.  If not, the assumption is that an instance of this application is already running.  The new instance must notify the existing instance that a launch was attempted, then exit.  The existing instance takes over after receiving the notification and fires an event to the listener that handles the action.

For security, I added a shared key that is used between the sender the receiver.  In my implementation, the only constraint is that it must end with a newline character.  That was just for ease of coding.  The imports are all in java.io or java.net.

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;
    }
}

public interface ApplicationInstanceListener {
    public void newInstanceCreated();
}

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...
          }
       });
    }
}

Categories

1 Comments

An alternative offers the (internal) sun.jvmstat package from tools.jar.

package com.tutego.jvmstat;

import java.util.Set;
import javax.swing.JOptionPane;
import sun.jvmstat.monitor.*;

public class JvmStat
{
@SuppressWarnings("unchecked")
public static void main( String[] args ) throws Exception
{
MonitoredHost monitoredhost = MonitoredHost.getMonitoredHost( "//localhost" );
for ( int id : (Set) monitoredhost.activeVms() )
{
VmIdentifier vmidentifier = new VmIdentifier( "" + id );
MonitoredVm monitoredvm = monitoredhost.getMonitoredVm( vmidentifier, 0 );
System.out.printf( "%d %s %s %s%n", id,
MonitoredVmUtil.mainClass( monitoredvm, true ),
MonitoredVmUtil.jvmArgs( monitoredvm ),
MonitoredVmUtil.mainArgs( monitoredvm ) );
}
}
}

Are there two occurrences of the result of MonitoredVmUtil.mainClass(), the program is started twice.

Christian

Leave a comment


Type the characters you see in the picture above.

Contact

If you find these articles helpful, please donate!
Every dollar is appreciated.