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 complete in standard Java language library. In the web you can find 2 basic approaches to make it work: port lock and file lock ideas. I've try to create really reusable piece of code :) which can handle cross application lock.
My code is based on the file lock idea. I think that port lock usage is not really reliable because of conflict possibility in ports usage.
Don't know why, but it always suggest 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'm try to solve this problems in this 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 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.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);
}
}
}



Comments:
It works just that i get this error when i exit my app
Jun 12, 2011 8:57:05 PM program.AppLock releaseLock
SEVERE: null
java.nio.channels.ClosedChannelException
at sun.nio.ch.FileLockImpl.release(FileLockImpl.java:54)
at program.AppLock.release(AppLock.java:101)
at program.AppLock.releaseLock(AppLock.java:173)
at program.AppLock$1.run(AppLock.java:159)
I don't know what is this really, but maybe I'm just closing locks in wrong sequence.
i think its fixed now and looks like this
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.GNOME.Accessibility.Application;
// TODO: Auto-generated Javadoc
/**
* The Class AppLock.
*
* @see http
* ://fresh2l.com/en/blog/2011/01/08/howto-run-only-single-java-application
* -instance
* @author http://fresh2L.com
* @version 1.0
*/
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
* the 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);
// Как правило нули в начале строки урезаются этот код добавляет
// нули обратно
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.
*
* @throws Throwable
* the 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();
}
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#finalize()
*/
@Override
protected void finalize() throws Throwable {
this.release();
super.finalize();
}
/** The instance. */
private static AppLock instance;
/**
* Sets the lock.
*
* @param key
* the 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(Application.class.getName()).log(Level.SEVERE,
"Broken off Applock", ex);
return false;
}
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
AppLock.releaseLock();
}
});
return true;
}
/**
* Release lock.
*
* @throws NoSuchFieldException
*/
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);
}
}
}
Sueoprir thinking demonstrated above. Thanks!
That's going to make tnhgis a lot easier from here on out.
the best approach i seen all day.
What about running on a mac. will it work?
If you have a new version of this code please post
I've actually test it under linux, Mac is also *nix system, so it should work there.
you did a grate job i must say truly Cross-platform.
Even when app has crashed the new app just pic up the existing md5.lock
If you have a pointer how, from the starting app before it exit(0)
tell the already running app to show itself, if the already running
app have the GUI set to setVisible(false).
Was thinking to have a Server.accept() to ping the already started
but that would probable lead to trouble if port is in use. One could of
cource set up a port number table to follow if first port is taken, but anyway..