Implement a single instance JAVA application
1. Use file lock to ensure single instance is running for this application.
2. Another file locking example:
2a.JustOneLock.java
2b. JustOneTest.java
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
3b. AppLock class
4. Socket technique
4a. Server
4b. Client
5. Another socket approach
5a. ApplicationInstanceManager class
5b. Listener Interface
5c. Main application
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
7b. Method getMoniteredVMs
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
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
留言
張貼留言