source

단일 인스턴스 Java 애플리케이션을 구현하려면 어떻게 해야 합니까?

lovecheck 2022. 10. 19. 21:13
반응형

단일 인스턴스 Java 애플리케이션을 구현하려면 어떻게 해야 합니까?

msn, Windows Media Player 등 단일 인스턴스 애플리케이션(애플리케이션 실행 중에 사용자가 실행해도 새 애플리케이션 인스턴스가 생성되지 않음)이 자주 나타납니다.

에서는 C#을 합니다.Mutex이거 수업인데 자바에서 어떻게 하는지 모르겠어요.

주요 방법으로는 다음과 같은 방법을 사용합니다.이 방법은 지금까지 본 방법 중 가장 단순하고 강력하며 침입이 적기 때문에 공유하려고 합니다.

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

가 이 기사를 믿는다면:

첫 번째 인스턴스가 로컬호스트 인터페이스에서 리스닝 소켓을 열려고 합니다.소켓을 열 수 있는 경우, 이것이 첫 번째로 실행되는 애플리케이션 인스턴스라고 가정합니다.그렇지 않은 경우 이 응용 프로그램의 인스턴스가 이미 실행 중인 것으로 가정합니다.새 인스턴스는 부팅이 시도되었음을 기존 인스턴스에 알린 후 종료해야 합니다.기존 인스턴스는 알림을 수신한 후 처리를 처리하는 리스너에 대한 이벤트를 실행합니다.

주의: 코멘트에서 다음과 같이 언급하고 있습니다.InetAddress.getLocalHost()까다로울 수 있습니다.

  • 반환되는 주소는 컴퓨터가 네트워크에 액세스할 수 있는지 여부에 따라 달라지기 때문에 DHCP 환경에서는 예상대로 작동하지 않습니다.
    해결책은 와의 접속을 여는 것이었습니다.
    버그 4435662와 관련이 있을 수 있습니다.
  • 버그 4665037도 찾았습니다.버그 4665037은 예상한 것보다 많은 결과를 보고합니다.getLocalHost IP 주소 결과:: 반환: 반환127.0.0.1

getLocalHost127.0.0.1Linux linux linux 、 Windows linux linux linux linux linux 。


또는 오브젝트를 사용할 수 있습니다.아래 설명과 같이:

getMonitoredVMs(int processPid)PID를 를 들어 은 메서드에서 되었습니다.예를 들어 어플리케이션의 시작은 다음과 같습니다.c:\java\app\test.jarpath를 하면 값 경로"가 됩니다.c:\\java\\app\\test.jar이렇게 하면 아래 코드 17행에서 어플리케이션 이름만 잡습니다.
그런 다음 JVM에서 동일한 이름의 다른 프로세스를 검색합니다. 이 프로세스를 찾아 애플리케이션 PID가 다르면 두 번째 애플리케이션 인스턴스를 의미합니다.

JNLP는 또한

, j it GUI the the theJWS하고 GUI를 사용합니다.SingleInstanceService.

갱신하다

Java Plug-In(애플릿과 JWS 앱 모두에 필요)은 Oracle에 의해 폐지되어 JDK에서 삭제되었습니다.브라우저 제조원은 이미 브라우저에서 이를 삭제했습니다.

그래서 이 대답은 사라졌습니다.오래된 문서를 보고 있는 사람들에게 경고하기 위해 남겨둔 것뿐입니다.

네, 이클립스 RCP 이클립스 싱글 인스턴스 어플리케이션은 제 코드입니다.

응용 프로그램 중자바

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 포트 XXXX에서 리스너를 엽니다.
  • 실패할 경우 localhost의 해당 포트에 대한 라이터를 열고 명령줄 args를 전송한 후 셧다운합니다.
  • 그렇지 않으면 localhost의 포트 XXXXX에서 수신합니다.명령줄 arg를 수신하면 해당 명령줄에서 앱을 실행한 것처럼 처리합니다.

나는 약간의 만화 같은 해답을 찾았지만, 대부분의 경우 여전히 효과가 있다.오래된 일반 잠금파일을 사용하여 작성하지만 다음과 같은 보기에서는 전혀 다릅니다.

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의 랜덤 포트에 서버 소켓을 만듭니다.

Windows 에서는, launch4j 를 사용할 수 있습니다.

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

멀티캐스트 소켓을 사용하면 임의의 양의 응용 프로그램인스턴스에 메시지를 브로드캐스트할 수 있습니다.이 중 일부는 기업 네트워크상의 물리적인 리모트머신 상에 있을 수 있습니다.

이 방법으로 다양한 유형의 구성을 활성화하여 다음과 같은 기능을 제어할 수 있습니다.

  • 머신당 1개 또는 여러 인스턴스
  • 네트워크당 1개 또는 여러 인스턴스(클라이언트 사이트 설치 제어 등)

Java의 멀티캐스트 지원은 java.net 패키지를 통해 이루어지며, Multicast Socket과 Datagram Socket이 주요 도구입니다.

주의: Multicast Socket은 데이터 패킷 전송을 보증하지 않으므로 JGroups와 같은 멀티캐스트소켓 위에 구축된 도구를 사용해야 합니다.JGroups는 모든 데이터의 전달을 보증합니다.이것은 매우 간단한 API를 가진 하나의 jar 파일입니다.

JGroups는 꽤 오래되었으며, 예를 들어 JBoss의 클러스터링 메커니즘이 클러스터의 모든 인스턴스에 데이터를 브로드캐스트하는 등 업계에서 몇 가지 인상적인 용도를 가지고 있습니다.

JGroups를 사용하는 방법은 개념적으로 매우 간단합니다(머신 또는 네트워크의 경우: 고객이 구매한 라이센스 수로 제한).

  • 응용 프로그램을 시작하면 각 인스턴스는 "My Great App Group"과 같은 명명된 그룹에 가입하려고 합니다.0, 1 또는 N 멤버를 허용하도록 이 그룹을 구성했습니다.
  • 그룹 멤버 수가 설정한 수보다 많은 경우.앱의 기동을 거부합니다.

J2SE 5.0 이상에서 지원되는 Management Factory 클래스

하지만 지금은 J2SE 1.4를 사용하고 있으며, http://audiprimadhanty.wordpress.com/2008/06/30/ensuring-one-instance-of-application-running-at-one-time/라는 것을 찾았지만 테스트한 적은 없습니다.당신은 그것에 대해 어떻게 생각하세요?

https://sanyarnd.github.io/applocker 전용 라이브러리를 작성했습니다.

파일 채널 잠금을 기반으로 하기 때문에 포트 번호를 차단하거나 정전(프로세스가 종료되면 채널이 해방됨)이 발생해도 애플리케이션을 교착 상태로 만들지 않습니다.

라이브러리는 그 자체로 가볍고 API가 유창합니다.

http://www.sauronsoftware.it/projects/junique/,에서 영감을 얻었지만 파일 채널을 기반으로 합니다.또, 그 외의 신기능도 있습니다.

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

다른 방법으로는 잠금 파일(표준 unix 프랙티스)을 사용하는 방법이 있습니다.또 하나의 방법은 클립보드에 이미 무엇이 있는지 확인한 후 메인이 시작될 때 클립보드에 무언가를 넣는 것입니다.

또는 리슨 모드(ServerSocket)로 소켓을 열 수 있습니다.먼저 hte 소켓에 접속을 시도합니다.접속할 수 없는 경우는 서버 소켓을 엽니다.접속하면 다른 인스턴스가 이미 실행 중임을 알 수 있습니다.

따라서 거의 모든 시스템 리소스를 사용하여 앱이 실행 중임을 알 수 있습니다.

BR, ~A

Preferences API를 사용해 볼 수 있습니다.플랫폼에 의존하지 않습니다.

소켓을 사용했는데 애플리케이션이 클라이언트 측인지 서버 측인지에 따라 동작이 조금 다릅니다.

  • client side : 인스턴스가 이미 존재하는 경우(특정 포트로 수신할 수 없음) 응용 프로그램 파라미터를 전달하고 종료합니다(이전 인스턴스에서 몇 가지 액션을 수행할 수 있습니다).그렇지 않은 경우 응용 프로그램을 시작합니다.
  • server side : 인스턴스가 이미 존재하는 경우 메시지를 인쇄하고 종료합니다.인스턴스가 존재하지 않는 경우 응용 프로그램을 시작합니다.

Unique4j 라이브러리는 Java 응용 프로그램의 단일 인스턴스를 실행하고 메시지를 전달하기 위해 사용할 수 있습니다.https://github.com/prat-man/unique4j 에서 보실 수 있습니다.Java 1.6+를 지원합니다.

파일 잠금과 동적 포트 잠금을 조합하여 인스턴스를 검출하고 서로 통신합니다.인스턴스는 1개만 실행할 수 있도록 하는 것이 주된 목표입니다.

다음은 같은 내용의 간단한 예입니다.

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 라이브러리를 작성 및 관리하고 있습니다.


퍼블릭 클래스 SingleInstance {퍼블릭 스태틱 최종 String LOCK = System.getProperty("user.home") + File.separator + "test.lock";
공용 정적 최종 String PIPE = System.getProperty("user.home") + File.separator + "test.pipe";
개인 정적 JFrame 프레임 = null;
public static void main(String[] args) {{을(를) 시험해 보다FileChannel lockChannel = 새로운 랜덤 액세스 파일(LOCK, "rw").getChannel();FileLock flk = null;{을(를) 시험해 보다flk = lock Channel.tryLock();} 캐치(투척 가능 t) {t.print Stack Trace();}if (flk == null || !flk.isValid()) {System.out.println("alread running, 파이프에 메시지를 남기고 종료...");FileChannel pipeChannel = null;{을(를) 시험해 보다pipeChannel = 새 RandomAccessFile(PIPE, "rw").getChannel();MappedByteBuffer bb = pipeChannel.map(파일채널).맵 모드READ_WRITE, 0, 1);bb.put(0, (바이트)1);
bb.force();} 캐치(투척 가능 t) {t.print Stack Trace();} 드디어 {if (파이프 채널!= null) {{을(를) 시험해 보다pipe Channel.close();} 캐치(투척 가능 t) {t.print Stack Trace();}}}System.exit(0);
}//여기에서는 잠금을 해제하고 채널을 닫지 않습니다.// 응용 프로그램이 정상적으로 크래시 또는 종료된 후에 수행됩니다. 
Swing Utilities.Later를 호출하다새로운 실행 가능() {public void run() {createAndShowGUI();}});
FileChannel pipeChannel = null;{을(를) 시험해 보다pipeChannel = 새 RandomAccessFile(PIPE, "rw").getChannel();MappedByteBuffer bb = pipeChannel.map(파일채널).맵 모드READ_WRITE, 0, 1);반면 (참) {바이트 b = bb.get(0);
(b > 0)의 경우,bb.put(0, (바이트)0);bb.force();Swing Utilities.Later를 호출하다새로운 실행 가능() {public void run() {frame.set Extended State(JFrame).정상);틀.setAlwaysOnTop(true);
frame.toFront();틀.setAlwaysOnTop(false);
}});}실.sleep(1000);
}} 캐치(투척 가능 t) {t.print Stack Trace();} 드디어 {if (파이프 채널!= null) {{을(를) 시험해 보다pipe Channel.close();} 캐치(투척 가능 t) {t.print Stack Trace();}}}} 캐치(투척 가능 t) {t.print Stack Trace();}}
public static void createAndShowGUI() {
frame = new JFrame();틀.setDefaultCloseOperation(JFrame).EXIT_ON_CLOSE);
틀.set Size (800, 650);frame.getContentPane().add(새로운 JLabel("메인 윈도")),Swing Constants.중앙), Border Layout(보더레이아웃).센터);
틀.set Location Relative To(null);
틀.setVisible(true);
}}

편집: 이 Watch Service 접근 방식을 사용하는 대신 간단한 1초 타이머 스레드를 사용하여 indicator File.exists() 여부를 확인할 수 있습니다.삭제 후 응용 프로그램을 Front()로 가져옵니다.

편집: 나는 왜 이것이 다운투표 되었는지 알고 싶다.지금까지 본 솔루션 중 최고입니다.예를 들어, 다른 애플리케이션이 이미 포트를 리슨하고 있는 경우 서버 소켓접근법이 실패합니다.

Microsoft Windows Sysinternals TCPView 를 다운로드(또는 netstat 를 사용)해, 기동해 「State」 로 정렬해, 「LISTENING」라고 하는 행 블록을 검색해, 리모트 주소에 컴퓨터의 이름이 기재되어 있는 것을 선택해, 그 포토를 새로운 Socket() 솔루션에 넣습니다.그것을 실장하는 것으로, 매번 실패하는 일이 있습니다.그리고 그것은 논리적이죠. 왜냐하면 그것은 접근법의 기초이기 때문입니다.또는 이 구현 방법에 대해 얻을 수 없는 것은 무엇입니까?

제가 어떻게 틀렸는지 알려주세요!

가능하다면 반증해 주셨으면 하는 제 견해는, 개발자에게 프로덕션 코드의 어프로치를 사용하라는 권고를 받고 있다는 것입니다만, 이 어프로치는 약 6만건 중 적어도 1건에서 실패하게 됩니다.만약 이 견해가 맞다면, 이 문제가 없는 솔루션이 다운 투표되어 코드의 양에 대해 비판을 받는 것은 결코 아닐 것입니다.

소켓 접근법의 단점은 다음과 같습니다.

  • 잘못된 복권(포트 번호)을 선택한 경우 실패합니다.
  • 다중 사용자 환경에서 실패:애플리케이션을 동시에 실행할 수 있는 사용자는 1명뿐입니다.(사용자 트리에 파일을 작성하려면 접근 방식을 약간 변경해야 합니다만, 이것은 간단한 일입니다.)
  • 방화벽 규칙이 너무 엄격하면 실패합니다.
  • 의심 많은 사용자(야생에서 만난 적이 있음)에게 텍스트 에디터가 서버 소켓을 요구하고 있을 때 무엇을 하고 있는지 궁금하게 합니다.

새로운 인스턴스에서 기존 인스턴스로의 Java 통신 문제를 모든 시스템에서 작동하는 방식으로 해결할 수 있는 좋은 아이디어가 떠올랐습니다.그래서 두 시간 만에 이 수업을 준비했어요.마법처럼 기능 : 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();
        }
    }

}

언급URL : https://stackoverflow.com/questions/177189/how-to-implement-a-single-instance-java-application

반응형