سؤال

في وقت ما أرى العديد من التطبيقات مثل msn وwindows media player وما إلى ذلك والتي تعد تطبيقات ذات مثيل واحد (عندما يقوم المستخدم بالتنفيذ أثناء تشغيل التطبيق، لن يتم إنشاء مثيل تطبيق جديد).

في C# أستخدم Mutex فئة لهذا ولكن لا أعرف كيفية القيام بذلك في جافا.

هل كانت مفيدة؟

المحلول

إذا كنت أعتقد هذا شرط, ، بواسطة :

محاولة المثيل الأول لفتح مأخذ توصيل استماع على واجهة المضيف المحلي.إذا كان قادرًا على فتح المقبس، فمن المفترض أن هذا هو المثيل الأول للتطبيق الذي سيتم تشغيله.إذا لم يكن الأمر كذلك، فإن الافتراض هو أن مثيل هذا التطبيق قيد التشغيل بالفعل.يجب أن يقوم المثيل الجديد بإعلام المثيل الحالي بمحاولة التشغيل، ثم الخروج.يتولى المثيل الموجود المسؤولية بعد تلقي الإشعار ويطلق حدثًا للمستمع الذي يتعامل مع الإجراء.

ملحوظة: اه يذكر في التعليق أن استخدام 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 أيضًا أ 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;
}

إذا كان التطبيق.يحتوي على واجهة مستخدم رسومية، قم بتشغيله باستخدام JWS واستخدم SingleInstanceService.انظر تجريبي.من SingleInstanceService ل (تجريبي.و) رمز المثال.

Yes this is a really decent answer for eclipse RCP eclipse single instance application below is my code

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

نحن نستخدم قفل الملف لهذا الغرض (احصل على قفل حصري لملف سحري في دليل بيانات التطبيق الخاص بالمستخدم)، ولكننا مهتمون في المقام الأول بمنع تشغيل مثيلات متعددة على الإطلاق.

إذا كنت تحاول الحصول على المثيل الثاني لتمرير وسيطات سطر الأوامر، وما إلى ذلك ...في المقام الأول، فإن استخدام اتصال المقبس على المضيف المحلي سيؤدي إلى قتل عصفورين بحجر واحد.الخوارزمية العامة:

  • عند التشغيل، حاول فتح المستمع على المنفذ XXXX على المضيف المحلي
  • إذا فشلت، افتح كاتبًا على ذلك المنفذ على المضيف المحلي وأرسل وسيطات سطر الأوامر، ثم أوقف التشغيل
  • بخلاف ذلك، استمع على المنفذ XXXX على المضيف المحلي.عند تلقي وسيطات سطر الأوامر، قم بمعالجتها كما لو تم تشغيل التطبيق باستخدام سطر الأوامر هذا.

لقد وجدت حلاً، وهو شرح كرتوني بعض الشيء، لكنه لا يزال يعمل في معظم الحالات.يستخدم ملف القفل القديم العادي لإنشاء الأشياء، ولكن بطريقة عرض مختلفة تمامًا:

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

أعتقد أنه سيكون مفيدًا لأولئك الذين لديهم إعداد صارم لجدار الحماية.

يمكنك استخدام مكتبة JUnique.يوفر الدعم لتشغيل تطبيق Java أحادي المثيل وهو مفتوح المصدر.

http://www.sauronsoftware.it/projects/junique/

The JUnique library can be used to prevent a user to run at the same time more instances of the same Java application.

JUnique implements locks and communication channels shared between all the JVM instances launched by the same user.

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.

على نظام التشغيل Windows، يمكنك استخدام Launch4j.

فئة ManagementFactory مدعومة في J2SE 5.0 أو الأحدث التفاصيل

لكن الآن أستخدم J2SE 1.4 ووجدت هذا http://audiprimadhanty.wordpress.com/2008/06/30/ensuring-one-instance-of-application-running-at-one-time/ لكنني لم أختبر أبدًا.ما رأيك في ذلك؟

يمكنك تجربة استخدام Preferences API.إنها منصة مستقلة.

هناك طريقة أكثر عمومية للحد من عدد المثيلات على جهاز واحد، أو حتى على الشبكة بأكملها، وهي استخدام مقبس البث المتعدد.

باستخدام مقبس البث المتعدد، يمكنك بث رسالة إلى أي عدد من مثيلات التطبيق الخاص بك، والتي يمكن أن يكون بعضها على أجهزة بعيدة فعليًا عبر شبكة الشركة.

بهذه الطريقة يمكنك تمكين العديد من أنواع التكوينات للتحكم في أشياء مثل

  • مثيل واحد أو عدة مثيلات لكل جهاز
  • مثيل واحد أو عدة مثيلات لكل شبكة (على سبيل المثال، التحكم في عمليات التثبيت على موقع العميل)

دعم البث المتعدد لـ Java عبر حزمة جافا.نت مع مقبس البث المتعدد & DatagramSocket كونها الأدوات الرئيسية.

ملحوظة:لا تضمن MulticastSocket تسليم حزم البيانات، لذا يجب عليك استخدام أداة مبنية على مآخذ البث المتعدد مثل JGroups.JGroups يفعل ضمان تسليم جميع البيانات.إنه ملف jar واحد، مع واجهة برمجة تطبيقات بسيطة جدًا.

لقد كانت JGroups موجودة منذ فترة طويلة، ولها بعض الاستخدامات المثيرة للإعجاب في الصناعة، على سبيل المثال، فهي تدعم آلية التجميع الخاصة بـ JBoss وتقوم ببث البيانات إلى جميع مثيلات المجموعة.

لاستخدام JGroups، للحد من عدد مثيلات التطبيق (على جهاز أو شبكة، دعنا نقول:إلى عدد التراخيص التي اشتراها العميل) أمر بسيط للغاية من الناحية النظرية:

  • عند بدء تشغيل التطبيق الخاص بك، يحاول كل مثيل الانضمام إلى مجموعة مسماة، على سبيل المثال "My Great App Group".ستكون قد قمت بتكوين هذه المجموعة للسماح بـ 0 أو 1 أو N من الأعضاء
  • عندما يكون عدد أعضاء المجموعة أكبر مما قمت بتكوينه لها..يجب أن يرفض تطبيقك بدء التشغيل.

يمكنك فتح ملف Memory Mapped ثم معرفة ما إذا كان هذا الملف مفتوحًا بالفعل.إذا كان مفتوحا بالفعل، يمكنك العودة من الرئيسي.

هناك طرق أخرى تتمثل في استخدام ملفات القفل (ممارسة يونكس القياسية).هناك طريقة أخرى وهي وضع شيء ما في الحافظة عند بدء التشغيل الرئيسي بعد التحقق مما إذا كان هناك شيء ما موجود بالفعل في الحافظة.

بخلاف ذلك، يمكنك فتح مأخذ توصيل في وضع الاستماع (ServerSocket).حاول أولاً الاتصال بمقبس 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 هذا، يمكن استخدام مؤشر ترابط بسيط مدته ثانية واحدة للتحقق مما إذا كان IndexFile.exists().احذفه، ثم أحضر التطبيق إلىFront().

يحرر:أود أن أعرف سبب التصويت السلبي لهذا.إنه أفضل حل رأيته حتى الآن.على سبيل المثاليفشل نهج مأخذ توصيل الخادم إذا حدث أن هناك تطبيق آخر يستمع بالفعل إلى المنفذ.

فقط قم بتنزيل Microsoft Windows Sysinternals TCPView (أو استخدم netstat)، ابدأ تشغيله، وقم بالفرز حسب "الحالة"، وابحث عن كتلة السطر التي تقول "LISTENING"، واختر واحدًا يشير عنوانه البعيد إلى اسم جهاز الكمبيوتر الخاص بك، ثم ضع هذا المنفذ في حل new-Socket() الخاص بك.وفي تنفيذي له، يمكنني أن أتسبب في الفشل في كل مرة.وهو كذلك منطقي, ، لأنه أساس النهج.أو ما الذي لا أحصل عليه فيما يتعلق بكيفية تنفيذ ذلك؟

الرجاء إبلاغي إذا كنت مخطئًا في هذا الأمر وكيف!

وجهة نظري - التي أطلب منك دحضها إن أمكن - هي أنه يُنصح المطورون باستخدام أسلوب في كود الإنتاج سيفشل في حالة واحدة على الأقل من حوالي 60000 حالة.وإذا صح هذا الرأي، فإنه يجوز ذلك بالتأكيد لا يكون الحل المقدم الذي لا يحتوي على هذه المشكلة قد تم التصويت عليه وانتقاده بسبب كمية التعليمات البرمجية الخاصة به.

عيوب نهج المقبس بالمقارنة:

  • يفشل إذا تم اختيار تذكرة اليانصيب الخاطئة (رقم المنفذ).
  • فشل في بيئة متعددة المستخدمين:يمكن لمستخدم واحد فقط تشغيل التطبيق في نفس الوقت.(يجب تغيير أسلوبي قليلاً لإنشاء الملف (الملفات) في شجرة المستخدم، ولكن هذا أمر تافه.)
  • يفشل إذا كانت قواعد جدار الحماية صارمة للغاية.
  • يجعل المستخدمين المشبوهين (الذين التقيت بهم في البرية) يتساءلون عن الخدع التي تقوم بها عندما يطالب محرر النصوص الخاص بك بمقبس خادم.

لقد خطرت لي فكرة رائعة عن كيفية حل مشكلة اتصال Java من مثيل جديد إلى مثيل موجود بطريقة ينبغي أن تعمل على كل نظام.لذلك، قمت بإنهاء هذا الفصل في حوالي ساعتين.يعمل مثل السحر :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