때때로 단일 인스턴스 응용 프로그램 인 msn, Windows Media Player 등과 같은 많은 응용 프로그램을 볼 수 있습니다 (응용 프로그램이 실행되는 동안 사용자가 실행하면 새 응용 프로그램 인스턴스가 생성되지 않습니다).
C #에서는이를 위해 Mutex
클래스를 사용 하지만 Java에서이 작업을 수행하는 방법을 모릅니다.
답변:
나는이 생각되면 글 작성자 :
첫 번째 인스턴스가 localhost 인터페이스에서 청취 소켓을 열려고 시도하도록합니다. 소켓을 열 수있는 경우 이것이 시작될 애플리케이션의 첫 번째 인스턴스라고 가정합니다. 그렇지 않은 경우이 응용 프로그램의 인스턴스가 이미 실행 중이라고 가정합니다. 새 인스턴스는 기존 인스턴스에 시작이 시도되었음을 알리고 종료해야합니다. 알림을받은 후 기존 인스턴스가 인계 받아 작업을 처리하는 리스너에 이벤트를 발생시킵니다.
참고 : Ahe 는 주석에서 사용 InetAddress.getLocalHost()
이 까다로울 수 있다고 언급합니다 .
- 반환 된 주소는 컴퓨터에 네트워크 액세스 권한이 있는지 여부에 따라 달라지기 때문에 DHCP 환경에서 예상대로 작동하지 않습니다.
해결책은 연결을 여는 것이 었습니다InetAddress.getByAddress(new byte[] {127, 0, 0, 1})
.
아마도 버그 4435662 와 관련이 있을 것 입니다.
getLocalHost
: return IP address of machine, vs. Actual results : return 127.0.0.1
. Linux에서는
getLocalHost
반환127.0.0.1
되지만 Windows 에서는 반환 되지 않는 것이 놀랍습니다 .
또는 ManagementFactory
개체를 사용할 수 있습니다 . 여기에 설명 된대로 :
이
getMonitoredVMs(int processPid)
메서드는 현재 응용 프로그램 PID를 매개 변수로 받고 명령 줄에서 호출 된 응용 프로그램 이름을 포착합니다 (예 : 응용 프로그램이c:\java\app\test.jar
경로에서 시작된 경우 값 변수는 "c:\\java\\app\\test.jar
"). 이렇게하면 아래 코드의 17 행에서 애플리케이션 이름 만 잡을 수 있습니다.
그 후 JVM에서 동일한 이름을 가진 다른 프로세스를 검색합니다. 발견하고 애플리케이션 PID가 다르면 두 번째 애플리케이션 인스턴스임을 의미합니다.
JNLP는 또한 SingleInstanceListener
InetAddress.getLocalHost()
반환 된 주소는 컴퓨터가 네트워크에 액세스 할 수 있는지 여부에 따라 달라지기 때문에 DHCP 환경에서 예상대로 작동하지 않는 것을 최근에 발견했습니다 . 해결책은와의 연결을 여는 것입니다 InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
.
InetAddress.getByName(null)
루프백 인터페이스의 주소를 반환합니다. 이론상 IPv6 전용 환경에서도 작동해야하므로 127.0.0.1을 수동으로 지정하는 것보다 이것이 더 낫다고 생각합니다.
나는 주요 방법에서 다음 방법을 사용합니다. 이것은 내가 본 것 중 가장 간단하고, 가장 강력하며, 가장 덜 방해가되는 방법이므로 공유 할 것이라고 생각했습니다.
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;
}
앱이라면. GUI가 있고 JWS로 시작하고 SingleInstanceService
.
Java 플러그인 (애플릿 및 JWS 앱 모두에 필요)은 Oracle에서 더 이상 사용되지 않으며 JDK에서 제거되었습니다. 브라우저 제조업체는 이미 브라우저에서이를 제거했습니다.
그래서이 대답은 없어졌습니다. 오래된 문서를 보는 사람들에게 경고하기 위해 여기에 두십시오.
예 이것은 이클립스 RCP 이클립스 단일 인스턴스 응용 프로그램에 대한 정말 괜찮은 대답입니다.
application.java에서
if(!isFileshipAlreadyRunning()){
MessageDialog.openError(display.getActiveShell(), "Fileship already running", "Another instance of this application is already running. Exiting.");
return IApplication.EXIT_OK;
}
private static boolean isFileshipAlreadyRunning() {
// socket concept is shown at http://www.rbgrn.net/content/43-java-single-application-instance
// but this one is really great
try {
final File file = new File("FileshipReserved.txt");
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;
}
이를 위해 파일 잠금을 사용하지만 (사용자의 앱 데이터 디렉토리에있는 매직 파일에 대한 배타적 잠금을 확보) 주로 여러 인스턴스가 실행되는 것을 방지하는 데 관심이 있습니다.
두 번째 인스턴스가 명령 줄 인수 등을 첫 번째 인스턴스에 전달하도록하려는 경우 localhost에서 소켓 연결을 사용하면 한 돌로 두 마리의 새를 죽일 수 있습니다. 일반 알고리즘 :
나는 해결책, 약간의 만화적인 설명을 찾았지만 대부분의 경우 여전히 작동합니다. 그것은 물건을 만드는 평범한 오래된 잠금 파일을 사용하지만 완전히 다른 관점에서 :
http://javalandscape.blogspot.com/2008/07/single-instance-from-your-application.html
방화벽 설정이 엄격한 분들에게 도움이 될 것 같습니다.
JUnique 라이브러리를 사용할 수 있습니다. 단일 인스턴스 Java 애플리케이션 실행을 지원하며 오픈 소스입니다.
http://www.sauronsoftware.it/projects/junique/
JUnique 라이브러리는 사용자가 동일한 Java 애플리케이션의 더 많은 인스턴스를 동시에 실행하는 것을 방지하는 데 사용할 수 있습니다.
JUnique는 동일한 사용자가 시작한 모든 JVM 인스턴스간에 공유되는 잠금 및 통신 채널을 구현합니다.
public static void main(String[] args) {
String appId = "myapplicationid";
boolean alreadyRunning;
try {
JUnique.acquireLock(appId, new MessageHandler() {
public String handle(String message) {
// A brand new argument received! Handle it!
return null;
}
});
alreadyRunning = false;
} catch (AlreadyLockedException e) {
alreadyRunning = true;
}
if (!alreadyRunning) {
// Start sequence here
} else {
for (int i = 0; i < args.length; i++) {
JUnique.sendMessage(appId, args[0]));
}
}
}
내부적으로는 % USER_DATA % /. junique 폴더에 파일 잠금을 생성하고 Java 응용 프로그램간에 메시지를주고받을 수 있도록 고유 한 각 appId에 대해 임의의 포트에 서버 소켓을 생성합니다.
J2SE 5.0 이상에서 지원되는 ManagementFactory 클래스 세부 사항
하지만 지금은 J2SE 1.4를 사용하고 http://audiprimadhanty.wordpress.com/2008/06/30/ensuring-one-instance-of-application-running-at-one-time/을 찾았 지만 테스트하지 않습니다. 당신이 그것에 대해 어떻게 생각하십니까?
단일 시스템 또는 전체 네트워크에서 인스턴스 수를 제한하는보다 일반적인 방법은 멀티 캐스트 소켓을 사용하는 것입니다.
멀티 캐스트 소켓을 사용하면 애플리케이션의 모든 인스턴스에 메시지를 브로드 캐스트 할 수 있으며, 그중 일부는 회사 네트워크를 통해 물리적으로 원격 시스템에있을 수 있습니다.
이러한 방식으로 여러 유형의 구성을 활성화하여 다음과 같은 항목을 제어 할 수 있습니다.
Java의 멀티 캐스트 지원은 MulticastSocket 및 DatagramSocket 이 주요 도구 인 java.net 패키지 를 통해 이루어 집니다.
참고 : MulticastSocket은 데이터 패킷의 전달을 보장하지 않으므로 JGroups 와 같은 멀티 캐스트 소켓 위에 구축 된 도구를 사용해야합니다 . JGroups 는 모든 데이터의 전달을 보장합니다. 매우 간단한 API가있는 단일 jar 파일입니다.
JGroups는 한동안 업계에서 인상적인 용도로 사용되었습니다. 예를 들어 JBoss의 클러스터링 메커니즘이 클러스터의 모든 인스턴스에 데이터를 브로드 캐스트하는 것을 뒷받침합니다.
JGroups를 사용하여 앱의 인스턴스 수를 제한하는 것은 개념적으로 매우 간단합니다.
메모리 매핑 파일을 열고 해당 파일이 이미 열려 있는지 확인할 수 있습니다. 이미 열려 있으면 메인에서 돌아올 수 있습니다.
다른 방법은 잠금 파일을 사용하는 것입니다 (표준 유닉스 관행). 한 가지 더 방법은 클립 보드에 이미 무언가가 있는지 확인한 후 main이 시작될 때 클립 보드에 무언가를 넣는 것입니다.
그렇지 않으면 청취 모드 (ServerSocket)에서 소켓을 열 수 있습니다. 먼저 소켓에 연결을 시도하십시오. 연결할 수 없으면 serversocket을 엽니 다. 연결하면 다른 인스턴스가 이미 실행 중임을 알 수 있습니다.
따라서 앱이 실행 중인지 확인하기 위해 거의 모든 시스템 리소스를 사용할 수 있습니다.
BR, ~ A
이를 위해 소켓을 사용했으며 응용 프로그램이 클라이언트 측인지 서버 측인지에 따라 동작이 약간 다릅니다.
공용 클래스 SingleInstance { public static final String LOCK = System.getProperty ( "user.home") + File.separator + "test.lock"; public static final String PIPE = System.getProperty ( "user.home") + File.separator + "test.pipe"; 개인 정적 JFrame 프레임 = null; public static void main (String [] args) { { FileChannel lockChannel = new RandomAccessFile (LOCK, "rw"). getChannel (); FileLock flk = null; { flk = lockChannel.tryLock (); } catch (Throwable t) { t.printStackTrace (); } if (flk == null ||! flk.isValid ()) { System.out.println ( "이미 실행 중입니다. 파이프에 메시지를 남기고 종료 중 ..."); FileChannel pipeChannel = null; { pipeChannel = new RandomAccessFile (PIPE, "rw"). getChannel (); MappedByteBuffer bb = pipeChannel.map (FileChannel.MapMode.READ_WRITE, 0, 1); bb.put (0, (바이트) 1); bb.force (); } catch (Throwable t) { t.printStackTrace (); } 드디어 { if (pipeChannel! = null) { { pipeChannel.close (); } catch (Throwable t) { t.printStackTrace (); } } } System.exit (0); } // 여기서 잠금을 해제하고 채널을 닫지 않습니다. // 애플리케이션이 충돌하거나 정상적으로 종료 된 후에 수행됩니다. SwingUtilities.invokeLater ( new Runnable () { public void run () { createAndShowGUI (); } } ); FileChannel pipeChannel = null; { pipeChannel = new RandomAccessFile (PIPE, "rw"). getChannel (); MappedByteBuffer bb = pipeChannel.map (FileChannel.MapMode.READ_WRITE, 0, 1); while (true) { 바이트 b = bb.get (0); if (b> 0) { bb.put (0, (바이트) 0); bb.force (); SwingUtilities.invokeLater ( new Runnable () { public void run () { frame.setExtendedState (JFrame.NORMAL); frame.setAlwaysOnTop (true); frame.toFront (); frame.setAlwaysOnTop (false); } } ); } Thread.sleep (1000); } } catch (Throwable t) { t.printStackTrace (); } 드디어 { if (pipeChannel! = null) { { pipeChannel.close (); } catch (Throwable t) { t.printStackTrace (); } } } } catch (Throwable t) { t.printStackTrace (); } } public static void createAndShowGUI () { 프레임 = new JFrame (); frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); frame.setSize (800, 650); frame.getContentPane (). add (new JLabel ( "MAIN WINDOW", SwingConstants.CENTER), BorderLayout.CENTER); frame.setLocationRelativeTo (null); frame.setVisible (true); } }
편집 :이 WatchService 접근 방식을 사용하는 대신 간단한 1 초 타이머 스레드를 사용하여 indicatorFile.exists ()가 있는지 확인할 수 있습니다. 삭제 한 다음 응용 프로그램을 Front ()로 가져옵니다.
편집 : 왜 이것이 비추천인지 알고 싶습니다. 지금까지 본 최고의 솔루션입니다. 예를 들어 다른 응용 프로그램이 이미 포트를 수신하고있는 경우 서버 소켓 접근이 실패합니다.
Microsoft Windows Sysinternals TCPView를 다운로드 (또는 netstat 사용)하고, 시작하고, "상태"별로 정렬하고, "LISTENING"이라고 표시된 줄 블록을 찾고, 원격 주소에 컴퓨터 이름이 표시된 것을 선택하고, 해당 포트를 새 소켓에 넣습니다. ()-해결책. 그것을 구현할 때 매번 실패를 일으킬 수 있습니다. 그리고 그것은 접근 방식의 근간이기 때문에 논리적 입니다. 아니면 이것을 구현하는 방법과 관련하여 무엇을 얻지 못합니까?
이것에 대해 내가 틀렸다면 알려주십시오!
가능한 한 반박 해달라고 요청하는 내 견해는 개발자가 약 60000 건 중 적어도 1 건에서 실패하는 프로덕션 코드에서 접근 방식을 사용하도록 조언 받고 있다는 것입니다. 그리고이 견해가 옳다면, 이 문제가없는 제시된 솔루션이 코드 양에 대해 비판을 받고 비판을받는 것은 절대로 아닐 수 있습니다.
비교에서 소켓 접근 방식의 단점 :
모든 시스템에서 작동하는 방식으로 새로운 인스턴스-기존 인스턴스 Java 통신 문제를 해결하는 방법에 대한 좋은 아이디어가있었습니다. 그래서 저는이 수업을 약 2 시간 만에 끝냈습니다. 매력처럼 작동 : D
이것은 Robert 의 파일 잠금 접근 방식 (또한이 페이지에 있음)을 기반으로 합니다. 이미 실행중인 인스턴스에 다른 인스턴스가 시작을 시도했지만 시작하지 않았 음을 알리기 위해 ... 파일이 생성되고 즉시 삭제되며 첫 번째 인스턴스는 WatchService를 사용하여이 폴더 내용 변경을 감지합니다. 문제가 얼마나 근본적인지를 감안할 때 이것이 새로운 아이디어라는 것을 믿을 수 없습니다.
이것은 그냥 만들기 위해 쉽게 변경할 수 있습니다파일을 하고 삭제하지 않도록 수 있으며, 적절한 인스턴스가 평가할 수있는 정보 (예 : 명령 줄 인수)를 입력 할 수 있으며 적절한 인스턴스가 삭제를 수행 할 수 있습니다. 개인적으로 애플리케이션의 창을 복원하고 앞으로 보낼 때만 알면됩니다.
사용 예 :
public static void main(final String[] args) {
// ENSURE SINGLE INSTANCE
if (!SingleInstanceChecker.INSTANCE.isOnlyInstance(Main::otherInstanceTriedToLaunch, false)) {
System.exit(0);
}
// launch rest of application here
System.out.println("Application starts properly because it's the only instance.");
}
private static void otherInstanceTriedToLaunch() {
// Restore your application window and bring it to front.
// But make sure your situation is apt: This method could be called at *any* time.
System.err.println("Deiconified because other instance tried to start.");
}
수업은 다음과 같습니다.
package yourpackagehere;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileLock;
import java.nio.file.*;
/**
* SingleInstanceChecker v[(2), 2016-04-22 08:00 UTC] by dreamspace-president.com
* <p>
* (file lock single instance solution by Robert https://stackoverflow.com/a/2002948/3500521)
*/
public enum SingleInstanceChecker {
INSTANCE; // HAHA! The CONFUSION!
final public static int POLLINTERVAL = 1000;
final public static File LOCKFILE = new File("SINGLE_INSTANCE_LOCKFILE");
final public static File DETECTFILE = new File("EXTRA_INSTANCE_DETECTFILE");
private boolean hasBeenUsedAlready = false;
private WatchService watchService = null;
private RandomAccessFile randomAccessFileForLock = null;
private FileLock fileLock = null;
/**
* CAN ONLY BE CALLED ONCE.
* <p>
* Assumes that the program will close if FALSE is returned: The other-instance-tries-to-launch listener is not
* installed in that case.
* <p>
* Checks if another instance is already running (temp file lock / shutdownhook). Depending on the accessibility of
* the temp file the return value will be true or false. This approach even works even if the virtual machine
* process gets killed. On the next run, the program can even detect if it has shut down irregularly, because then
* the file will still exist. (Thanks to Robert https://stackoverflow.com/a/2002948/3500521 for that solution!)
* <p>
* Additionally, the method checks if another instance tries to start. In a crappy way, because as awesome as Java
* is, it lacks some fundamental features. Don't worry, it has only been 25 years, it'll sure come eventually.
*
* @param codeToRunIfOtherInstanceTriesToStart Can be null. If not null and another instance tries to start (which
* changes the detect-file), the code will be executed. Could be used to
* bring the current (=old=only) instance to front. If null, then the
* watcher will not be installed at all, nor will the trigger file be
* created. (Null means that you just don't want to make use of this
* half of the class' purpose, but then you would be better advised to
* just use the 24 line method by Robert.)
* <p>
* BE CAREFUL with the code: It will potentially be called until the
* very last moment of the program's existence, so if you e.g. have a
* shutdown procedure or a window that would be brought to front, check
* if the procedure has not been triggered yet or if the window still
* exists / hasn't been disposed of yet. Or edit this class to be more
* comfortable. This would e.g. allow you to remove some crappy
* comments. Attribution would be nice, though.
* @param executeOnAWTEventDispatchThread Convenience function. If false, the code will just be executed. If
* true, it will be detected if we're currently on that thread. If so,
* the code will just be executed. If not so, the code will be run via
* SwingUtilities.invokeLater().
* @return if this is the only instance
*/
public boolean isOnlyInstance(final Runnable codeToRunIfOtherInstanceTriesToStart, final boolean executeOnAWTEventDispatchThread) {
if (hasBeenUsedAlready) {
throw new IllegalStateException("This class/method can only be used once, which kinda makes sense if you think about it.");
}
hasBeenUsedAlready = true;
final boolean ret = canLockFileBeCreatedAndLocked();
if (codeToRunIfOtherInstanceTriesToStart != null) {
if (ret) {
// Only if this is the only instance, it makes sense to install a watcher for additional instances.
installOtherInstanceLaunchAttemptWatcher(codeToRunIfOtherInstanceTriesToStart, executeOnAWTEventDispatchThread);
} else {
// Only if this is NOT the only instance, it makes sense to create&delete the trigger file that will effect notification of the other instance.
//
// Regarding "codeToRunIfOtherInstanceTriesToStart != null":
// While creation/deletion of the file concerns THE OTHER instance of the program,
// making it dependent on the call made in THIS instance makes sense
// because the code executed is probably the same.
createAndDeleteOtherInstanceWatcherTriggerFile();
}
}
optionallyInstallShutdownHookThatCleansEverythingUp();
return ret;
}
private void createAndDeleteOtherInstanceWatcherTriggerFile() {
try {
final RandomAccessFile randomAccessFileForDetection = new RandomAccessFile(DETECTFILE, "rw");
randomAccessFileForDetection.close();
Files.deleteIfExists(DETECTFILE.toPath()); // File is created and then instantly deleted. Not a problem for the WatchService :)
} catch (Exception e) {
e.printStackTrace();
}
}
private boolean canLockFileBeCreatedAndLocked() {
try {
randomAccessFileForLock = new RandomAccessFile(LOCKFILE, "rw");
fileLock = randomAccessFileForLock.getChannel().tryLock();
return fileLock != null;
} catch (Exception e) {
return false;
}
}
private void installOtherInstanceLaunchAttemptWatcher(final Runnable codeToRunIfOtherInstanceTriesToStart, final boolean executeOnAWTEventDispatchThread) {
// PREPARE WATCHSERVICE AND STUFF
try {
watchService = FileSystems.getDefault().newWatchService();
} catch (IOException e) {
e.printStackTrace();
return;
}
final File appFolder = new File("").getAbsoluteFile(); // points to current folder
final Path appFolderWatchable = appFolder.toPath();
// REGISTER CURRENT FOLDER FOR WATCHING FOR FILE DELETIONS
try {
appFolderWatchable.register(watchService, StandardWatchEventKinds.ENTRY_DELETE);
} catch (IOException e) {
e.printStackTrace();
return;
}
// INSTALL WATCHER THAT LOOKS IF OUR detectFile SHOWS UP IN THE DIRECTORY CHANGES. IF THERE'S A CHANGE, ANOTHER INSTANCE TRIED TO START, SO NOTIFY THE CURRENT ONE OF THAT.
final Thread t = new Thread(() -> watchForDirectoryChangesOnExtraThread(codeToRunIfOtherInstanceTriesToStart, executeOnAWTEventDispatchThread));
t.setDaemon(true);
t.setName("directory content change watcher");
t.start();
}
private void optionallyInstallShutdownHookThatCleansEverythingUp() {
if (fileLock == null && randomAccessFileForLock == null && watchService == null) {
return;
}
final Thread shutdownHookThread = new Thread(() -> {
try {
if (fileLock != null) {
fileLock.release();
}
if (randomAccessFileForLock != null) {
randomAccessFileForLock.close();
}
Files.deleteIfExists(LOCKFILE.toPath());
} catch (Exception ignore) {
}
if (watchService != null) {
try {
watchService.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
Runtime.getRuntime().addShutdownHook(shutdownHookThread);
}
private void watchForDirectoryChangesOnExtraThread(final Runnable codeToRunIfOtherInstanceTriesToStart, final boolean executeOnAWTEventDispatchThread) {
while (true) { // To eternity and beyond! Until the universe shuts down. (Should be a volatile boolean, but this class only has absolutely required features.)
try {
Thread.sleep(POLLINTERVAL);
} catch (InterruptedException e) {
e.printStackTrace();
}
final WatchKey wk;
try {
wk = watchService.poll();
} catch (ClosedWatchServiceException e) {
// This situation would be normal if the watcher has been closed, but our application never does that.
e.printStackTrace();
return;
}
if (wk == null || !wk.isValid()) {
continue;
}
for (WatchEvent<?> we : wk.pollEvents()) {
final WatchEvent.Kind<?> kind = we.kind();
if (kind == StandardWatchEventKinds.OVERFLOW) {
System.err.println("OVERFLOW of directory change events!");
continue;
}
final WatchEvent<Path> watchEvent = (WatchEvent<Path>) we;
final File file = watchEvent.context().toFile();
if (file.equals(DETECTFILE)) {
if (!executeOnAWTEventDispatchThread || SwingUtilities.isEventDispatchThread()) {
codeToRunIfOtherInstanceTriesToStart.run();
} else {
SwingUtilities.invokeLater(codeToRunIfOtherInstanceTriesToStart);
}
break;
} else {
System.err.println("THIS IS THE FILE THAT WAS DELETED: " + file);
}
}
wk.reset();
}
}
}
new ServerSocket()
캐치 블록을 사용하는 것이 매우 적절합니다.
Unique4j 라이브러리는 Java 애플리케이션의 단일 인스턴스를 실행하고 메시지를 전달하는 데 사용할 수 있습니다. https://github.com/prat-man/unique4j 에서 볼 수 있습니다 . Java 1.6 이상을 지원합니다.
파일 잠금 및 동적 포트 잠금의 조합을 사용하여 하나의 인스턴스 만 실행하도록 허용하는 기본 목표로 인스턴스 간을 감지하고 통신합니다.
다음은 동일한 간단한 예입니다.
import tk.pratanumandal.unique4j.Unique4j;
import tk.pratanumandal.unique4j.exception.Unique4jException;
public class Unique4jDemo {
// unique application ID
public static String APP_ID = "tk.pratanumandal.unique4j-mlsdvo-20191511-#j.6";
public static void main(String[] args) throws Unique4jException, InterruptedException {
// create unique instance
Unique4j unique = new Unique4j(APP_ID) {
@Override
public void receiveMessage(String message) {
// display received message from subsequent instance
System.out.println(message);
}
@Override
public String sendMessage() {
// send message to first instance
return "Hello World!";
}
};
// try to obtain lock
boolean lockFlag = unique.acquireLock();
// sleep the main thread for 30 seconds to simulate long running tasks
Thread.sleep(30000);
// try to free the lock before exiting program
boolean lockFreeFlag = unique.freeLock();
}
}
면책 조항 : Unique4j 라이브러리를 만들고 유지합니다.