단일 인스턴스 Java 응용 프로그램을 구현하는 방법은 무엇입니까?

StackOverflow https://stackoverflow.com/questions/177189

  •  05-07-2019
  •  | 
  •  

문제

때때로 나는 단일 인스턴스 애플리케이션 인 MSN, Windows Media Player 등과 같은 많은 응용 프로그램을 볼 수 있습니다 (응용 프로그램이 새로운 응용 프로그램 인스턴스를 실행하는 동안 사용자가 실행할 때).

C#에서는 사용합니다 Mutex 이것에 대한 수업이지만 Java 에서이 작업을 수행하는 방법을 모르겠습니다.

도움이 되었습니까?

해결책

내가 이것을 믿는다면 기사, 에 의해 :

첫 번째 인스턴스가 로컬 호스트 인터페이스에서 청취 소켓을 열려고합니다. 소켓을 열 수 있다면 이것이 출시 될 애플리케이션의 첫 번째 인스턴스라고 가정합니다. 그렇지 않은 경우,이 응용 프로그램의 인스턴스가 이미 실행되고 있다고 가정합니다. 새 인스턴스는 기존 인스턴스를 시작하여 시도한 다음 종료해야합니다. 기존 인스턴스는 알림을받은 후 인수를 인계하고 액션을 처리하는 청취자에게 이벤트를 시작합니다.

메모: 사용하는 의견에 언급합니다 InetAddress.getLocalHost() 까다로울 수 있습니다 :

  • 반환 된 주소는 컴퓨터에 네트워크 액세스가 있는지 여부에 따라 달라지기 때문에 DHCP 환경에서 예상대로 작동하지 않습니다.
    해결책은 연결을 열어주는 것이 었습니다 InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
    아마 관련이 있습니다 버그 4435662.
  • 나는 또한 발견했다 버그 4665037 예상 결과보다보고합니다 getLocalHost: 기계의 IP 주소 반환, 실제 결과 : 반환 127.0.0.1.

놀랍습니다 getLocalHost 반품 127.0.0.1 Linux에서는 그러나 Windows에서는 그렇지 않습니다.


또는 사용할 수도 있습니다 ManagementFactory 물체. 설명 된 바와 같이 여기:

그만큼 getMonitoredVMs(int processPid) 메소드는 현재 응용 프로그램 PID를 매개 변수로 수신하고 명령 줄에서 호출 된 응용 프로그램 이름을 포착합니다. 예를 들어 응용 프로그램이 시작되었습니다. c:\java\app\test.jar 경로, 값 변수는 "c:\\java\\app\\test.jar". 이런 식으로, 우리는 아래 코드 17 행에서 신청 이름 만 포착 할 것입니다.
그 후, 우리는 동일한 이름으로 다른 프로세스에 대해 JVM을 검색하고, 응용 프로그램 PID가 다르면 두 번째 응용 프로그램 인스턴스라는 것을 의미합니다.

JNLP는 또한 a SingleInstanceListener

다른 팁

주요 방법에서 다음 방법을 사용합니다. 이것은 내가 본 가장 단순하고 강력하고 가장 방해가되는 방법이므로 공유 할 것이라고 생각했습니다.

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. 참조 데모. SingleInstanceService의 (데모 및) 예제 코드 용.

예, 이클립스 RCP Eclipse 단일 인스턴스 응용 프로그램에 대한 정말 괜찮은 답은 내 코드입니다.

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

우리는 이것에 대해 파일 잠금을 사용하지만 (사용자의 앱 데이터 디렉토리의 마법 파일에 독점 잠금을 가져옵니다), 주로 여러 인스턴스가 실행되는 것을 방지하는 데 관심이 있습니다.

두 번째 인스턴스 패스 명령 줄 Args 등을 사용하려고하는 경우, 첫 번째 경우 LocalHost에서 소켓 연결을 사용하면 하나의 돌로 두 마리의 새를 죽일 것입니다. 일반 알고리즘 :

  • 출시시 LocalHost의 Port XXXX에서 리스너를 열어보십시오.
  • 실패하면 LocalHost의 해당 포트로 작가를 열고 명령 줄 Args를 보내고 종료
  • 그렇지 않으면 LocalHost의 Port XXXXX에서 듣습니다. 명령 행 Args를 수신하면 앱이 해당 명령 줄로 시작된 것처럼 처리하십시오.

나는 솔루션, 약간 만화적인 설명을 찾았지만 대부분의 경우에도 여전히 작동합니다. 평범한 오래된 잠금 파일을 사용하여 물건을 생성하지만 매우 다른 견해로는 다음과 같습니다.

http://javalandscape.blogspot.com/2008/07/single-instance-from-your-application.html

엄격한 방화벽 설정을 가진 사람들에게 도움이 될 것이라고 생각합니다.

Junique Library를 사용할 수 있습니다. 단일 인스턴스 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 폴더에서 파일 잠금을 생성하고 각 고유 한 appid에 대해 임의의 포트에 서버 소켓을 생성하여 Java 응용 프로그램간에 메시지를 보내거나 수신 할 수 있습니다.

Windows에서 사용할 수 있습니다 런치 4J.

J2SE 5.0 이상에서 지원되는 ManagementFactory 클래스 세부 사항

하지만 이제 나는 J2SE 1.4를 사용하고 이것을 찾았습니다. http://audiprimadhanty.wordpress.com/2008/06/30/ensuring-onstance-of-application-t-on-on- time/ 그러나 나는 결코 테스트하지 않습니다. 당신이 그것에 대해 어떻게 생각하십니까?

환경 설정 API를 사용해 볼 수 있습니다. 플랫폼 독립적입니다.

단일 시스템 또는 전체 네트워크에서 인스턴스 수를 제한하는보다 일반적인 방법은 멀티 캐스트 소켓을 사용하는 것입니다.

멀티 캐스트 소켓을 사용하면 애플리케이션의 모든 인스턴스에 메시지를 방송 할 수 있으며, 일부는 회사 네트워크의 물리적 원격 기계에있을 수 있습니다.

이런 식으로 여러 유형의 구성을 활성화하여 다음과 같은 것들을 제어 할 수 있습니다.

  • 기계 당 하나 또는 많은 인스턴스
  • 네트워크 당 하나 이상의 인스턴스 (예 : 클라이언트 사이트에서 설치 제어)

Java의 멀티 캐스트 지원은 VIA입니다 java.net 패키지 ~와 함께 멀티 캐스트 소켓 & Datagramsocket 주요 도구입니다.

메모: MulticastSocket은 데이터 패킷의 전달을 보장하지 않으므로 다음과 같은 멀티 캐스트 소켓 위에 구축 된 도구를 사용해야합니다. jgroups. jgroups 하다 모든 데이터의 전달을 보장합니다. 매우 간단한 API가있는 하나의 단일 JAR 파일입니다.

JGROUPS는 한동안 약이 있었으며 업계에서 인상적인 사용법을 가지고 있습니다. 예를 들어 JBoss의 클러스터링 메커니즘은 클러스터의 모든 인스턴스에 데이터를 방송합니다.

jgroups를 사용하려면 앱의 인스턴스 수를 제한하려면 (컴퓨터 또는 네트워크에서 : 고객이 구매 한 라이센스 수에) 개념적으로 매우 간단합니다.

  • 응용 프로그램이 시작되면 각 인스턴스는 이름이 지정된 그룹, 즉 "My Great App Group"에 가입하려고합니다. 0, 1 또는 N 멤버를 허용하도록이 그룹을 구성했을 것입니다.
  • 그룹 멤버 카운트가 구성 한 것보다 클 때. 앱은 시작을 거부해야합니다.

메모리 매핑 파일을 열고 해당 파일이 이미 열려 있는지 확인할 수 있습니다. 이미 열려있는 경우 메인에서 돌아올 수 있습니다.

다른 방법은 잠금 파일을 사용하는 것입니다 (표준 UNIX 실습). 한 가지 더 방법은 이미 클립 보드에 있는지 확인한 후 메인이 시작될 때 클립 보드에 무언가를 넣는 것입니다.

그렇지 않으면 청취 모드 (서버 소켓)에서 소켓을 열 수 있습니다. 먼저 HTE 소켓에 연결하십시오. 연결할 수없는 경우 서버 소켓을 엽니 다. 연결하면 다른 인스턴스가 이미 실행 중다는 것을 알고 있습니다.

따라서 앱이 실행되고 있음을 아는 데 거의 모든 시스템 리소스를 사용할 수 있습니다.

br, ~ a

나는 그것을 위해 소켓을 사용했고 응용 프로그램이 클라이언트 쪽 또는 서버쪽에 있는지에 따라 동작은 약간 다릅니다.

  • 클라이언트 측 : 인스턴스가 이미 존재하는 경우 (특정 포트에서들을 수 없음) 응용 프로그램 매개 변수를 전달하고 응용 프로그램을 시작하지 않으면 종료합니다 (이전 인스턴스에서 일부 작업을 수행 할 수 있음).
  • 서버 측 : 인스턴스가 이미 존재하면 메시지를 인쇄하고 종료합니다. 그렇지 않으면 응용 프로그램을 시작합니다.
public class 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";
    private static JFrame frame = null;

    public static void main(String[] args) {
        try {
            FileChannel lockChannel = new RandomAccessFile(LOCK, "rw").getChannel();
            FileLock flk = null; 
            try {
                flk = lockChannel.tryLock();
            } catch(Throwable t) {
                t.printStackTrace();
            }
            if (flk == null || !flk.isValid()) {
                System.out.println("alread running, leaving a message to pipe and quitting...");
                FileChannel pipeChannel = null;
                try {
                    pipeChannel = new RandomAccessFile(PIPE, "rw").getChannel();
                    MappedByteBuffer bb = pipeChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1);
                    bb.put(0, (byte)1);
                    bb.force();
                } catch (Throwable t) {
                    t.printStackTrace();
                } finally {
                    if (pipeChannel != null) {
                        try {
                            pipeChannel.close();
                        } catch (Throwable t) {
                            t.printStackTrace();
                        }
                    } 
                }
                System.exit(0);
            }
            //We do not release the lock and close the channel here, 
            //  which will be done after the application crashes or closes normally. 
            SwingUtilities.invokeLater(
                new Runnable() {
                    public void run() {
                        createAndShowGUI();
                    }
                }
            );

            FileChannel pipeChannel = null;
            try {
                pipeChannel = new RandomAccessFile(PIPE, "rw").getChannel();
                MappedByteBuffer bb = pipeChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1);
                while (true) {
                    byte b = bb.get(0);
                    if (b > 0) {
                        bb.put(0, (byte)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();
            } finally {
                if (pipeChannel != null) {
                    try {
                        pipeChannel.close();
                    } catch (Throwable t) {
                        t.printStackTrace();
                    } 
                } 
            }
        } catch(Throwable t) {
            t.printStackTrace();
        } 
    }

    public static void createAndShowGUI() {

        frame = 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 ()를 확인할 수 있습니다. 삭제 한 다음 응용 프로그램 tofront ()를 가져옵니다.

편집하다: 왜 이것이 왜 다운되었는지 알고 싶습니다. 지금까지 본 최고의 솔루션입니다. 예를 들어 다른 응용 프로그램이 이미 포트를 듣고있는 경우 서버 소켓 접근법이 실패합니다.

Microsoft Windows Sysinternals 만 다운로드하십시오 tcpview (또는 Netstat를 사용하고 시작하고, 시작하고, "상태"로 정렬하고, "청취"라는 라인 블록을 찾아, 원격 주소가 컴퓨터 이름을 말하는 사람을 선택하고, 해당 포트를 새 소켓 ()-솔루션에 넣으십시오. 그것을 구현할 때, 나는 매번 실패를 생성 할 수 있습니다. 그리고 그것은입니다 논리적, 그것은 접근 방식의 기초이기 때문에. 아니면 이것을 구현하는 방법에 대해 무엇을 얻지 못하고 있습니까?

내가 이것에 대해 내가 어떻게 잘못되었는지 알려주세요!

내 견해는 가능한 경우 반증하도록 요구하는 내 견해는 개발자가 약 60000 건 중 최소 1 개 이상에서 실패 할 생산 코드에서 접근 방식을 사용하는 것이 좋습니다. 그리고이 견해가 올바른 경우에는 절대적으로 ~ 아니다 이 문제가없는 제시된 솔루션은 코드 금액에 대해 검토되고 비판됩니다.

비교하여 소켓 접근의 단점 :

  • 잘못된 복권 (포트 번호)이 선택되면 실패합니다.
  • 다중 사용자 환경에서 실패 : 한 명의 사용자 만 동시에 응용 프로그램을 실행할 수 있습니다. (사용자 트리에서 파일을 만들려면 내 접근 방식이 약간 변경되어야하지만 사소한 일입니다.)
  • 방화벽 규칙이 너무 엄격한 경우 실패합니다.
  • 의심스러운 사용자 (내가 야생에서 만나는)가 텍스트 편집기가 서버 소켓을 청구 할 때 어떤 shenanigans에 대해 궁금해합니다.

방금 모든 시스템에서 작동 해야하는 방식으로 새로운 인스턴스-기존 인스턴스 Java 커뮤니케이션 문제를 해결하는 방법에 대한 좋은 아이디어를 얻었습니다. 그래서 나는이 수업을 약 2 시간 만에 채찍질했다. 매력처럼 작동합니다 : d

그것은 기반입니다 로버트이 페이지의 파일 잠금 방식 (이 페이지에도). 이미 실행중인 인스턴스에 다른 인스턴스가 시작하려고 시도했지만 (그러나하지 않았지만) 파일이 생성되고 즉시 삭제되고 첫 번째 인스턴스는 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();
        }
    }

}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top