Tek örnekli bir Java uygulaması nasıl uygulanır?


89

Bazen msn, windows media player vb. Gibi tek örnek uygulamalar olan birçok uygulama görüyorum (kullanıcı uygulama çalışırken yeni bir uygulama örneği çalıştırdığında oluşturulmayacaktır).

C # 'da bunun için Mutexsınıf kullanıyorum ama bunu Java'da nasıl yapacağımı bilmiyorum.


Java NIO ile çok basit bir yaklaşım tam örneğe bakın stackoverflow.com/a/20015771/185022
AZ_

Yanıtlar:


62

Bu makaleye inanıyorsam :

ilk örneğe localhost arabiriminde bir dinleme soketi açmayı denemek. Soketi açabiliyorsa, bunun başlatılacak uygulamanın ilk örneği olduğu varsayılır. Değilse, varsayım bu uygulamanın bir örneğinin zaten çalışıyor olmasıdır. Yeni örnek, mevcut örneğe bir başlatma denendiğini bildirmeli ve ardından çıkmalıdır. Mevcut örnek, bildirimi aldıktan sonra devreye girer ve eylemi gerçekleştiren dinleyiciye bir olay başlatır.

Not: Ahe , yorumda kullanmanın InetAddress.getLocalHost()zor olabileceğinden bahsediyor :

  • DHCP ortamında beklendiği gibi çalışmaz çünkü döndürülen adres bilgisayarın ağ erişimine sahip olup olmamasına bağlıdır.
    Çözüm ile bağlantı açmak oldu InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
    Muhtemelen hata 4435662 ile ilgilidir .
  • Ayrıca , Beklenen sonuçlardan daha fazlasını bildiren hata 4665037 buldum getLocalHost: makinenin IP adresini döndür , Gerçek sonuçlar: dönüş 127.0.0.1.

Windows'ta değil, Linux'ta getLocalHostgeri dönüş olması şaşırtıcı 127.0.0.1.


Ya da ManagementFactorynesneyi kullanabilirsiniz . As açıkladı Burada :

getMonitoredVMs(int processPid)Yöntem, mevcut uygulama PID parametresi olarak alır ve, örneğin, uygulama başlatıldı, komut satırı adlandırılan uygulama adı yakalamak c:\java\app\test.jaryolu, daha sonra değeri değişken "dir c:\\java\\app\\test.jar." Bu şekilde, aşağıdaki kodun 17. satırında sadece uygulama adını yakalayacağız.
Bundan sonra, JVM'de aynı isimli başka bir işlem ararız, eğer onu bulursak ve uygulama PID'si farklıysa, bu ikinci uygulama örneğidir demektir.

JNLP ayrıca bir SingleInstanceListener


3
İlk çözümde bir hata olduğunu unutmayın. Yakın zamanda bunun InetAddress.getLocalHost()DHCP ortamında beklendiği gibi çalışmadığını keşfettik çünkü döndürülen adres bilgisayarın ağ erişimine sahip olup olmamasına bağlıdır. Çözüm, ile bağlantı açmaktı InetAddress.getByAddress(new byte[] {127, 0, 0, 1});.
Ahe

2
@Ahe: mükemmel nokta. Yorumunuzu ve Oracle-Sun hata raporu referanslarını düzenlenmiş cevabıma ekledim.
VonC

3
JavaDoc'a göre, InetAddress.getByName(null)geri döngü arayüzünün adresini döndürür. Sanırım bu, 127.0.0.1'i manuel olarak belirlemekten daha iyidir çünkü teoride bu yalnızca IPv6 ortamlarında da çalışmalıdır.
kayahr


1
@Puce Elbette, sorun değil: Bu bağlantıları geri yükledim.
VonC

65

Ana yöntemde aşağıdaki yöntemi kullanıyorum. Bu gördüğüm en basit, en sağlam ve en az müdahaleci yöntem bu yüzden paylaşacağımı düşündüm.

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

bir masaüstü uygulaması için "lockFile" parametresi ne olmalıdır? uygulama jar dosyası adı? Jar dosyası yok sadece bazı sınıf dosyaları nasıl olur?
5YrsLaterDBA

3
Dosya kilidini manuel olarak serbest bırakmak ve kapatma sırasında dosyayı kapatmak gerçekten gerekli mi? İşlem öldüğünde bu otomatik olarak gerçekleşmez mi?
Natix

5
Peki güç kesilirse ve bilgisayar kapatma kancasını çalıştırmadan kapanırsa ne olur? Dosya devam edecek ve uygulama başlatılamayacaktır.
Petr Hudeček

7
@ PetrHudeček Sorun değil. Uygulamanın nasıl bittiği önemli değil, dosya kilidi kaldırılacaktır. Düzgün bir kapatma değilse, bu, uygulamanın bir sonraki çalıştırmada bunu gerçekleştirmesine izin verme avantajına bile sahiptir. Her durumda: Kilit, dosyanın kendisinin varlığı değil, önemli olan şeydir. Dosya hala oradaysa, uygulama yine de başlayacaktır.
Dreamspace President

@Robert: Çözümünüz için teşekkürler, o zamandan beri kullanıyorum. Ve şimdi, başka bir örneğin başlatmaya çalıştığı halihazırda var olan örneğe WatchService klasörünü kullanarak iletişim kuracak şekilde genişlettim! stackoverflow.com/a/36772436/3500521
Dreamspace Başkan

10

Uygulama. bir GUI'ye sahiptir, JWS ile başlatın ve SingleInstanceService.

Güncelleme

Java Eklentisi (hem uygulamalar hem de JWS uygulamaları için gereklidir) Oracle tarafından kullanımdan kaldırıldı ve JDK'dan kaldırıldı. Tarayıcı üreticileri bunu tarayıcılarından zaten kaldırmıştı.

Yani bu cevap geçersizdir. Sadece eski belgelere bakan insanları uyarmak için burada bırakmak.


2
Ayrıca, çalışan örneğin yeni örneklerden ve bunların argümanlarından haberdar edilebileceğini ve bu tür bir programla iletişim kurmayı kolaylaştırdığını unutmayın.
Thorbjørn Ravn Andersen

6

Evet, bu tutulma RCP tutulması için gerçekten iyi bir cevap tek örnekli uygulama aşağıdaki kodum

application.java'da

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

5

Bunun için dosya kilitlemeyi kullanıyoruz (kullanıcının uygulama veri dizinindeki sihirli bir dosyaya özel bir kilit yakala), ancak öncelikle birden fazla örneğin çalışmasını engellemekle ilgileniyoruz.

İkinci örneğin komut satırı değiştirgelerini, vb. İlk örneğe geçirmesini istiyorsanız, localhost'ta bir soket bağlantısı kullanmak bir taşla iki kuşu öldürür. Genel algoritma:

  • Başlatma sırasında, localhost üzerindeki XXXX bağlantı noktasındaki dinleyiciyi açmayı deneyin
  • başarısız olursa, localhost üzerindeki bu bağlantı noktasına bir yazıcı açın ve komut satırı değiştirgelerini gönderin, ardından kapatın
  • aksi takdirde, localhost üzerindeki XXXXX bağlantı noktasını dinleyin. Komut satırı bağımsız değişkenlerini aldığınızda, bunları uygulama o komut satırıyla başlatılmış gibi işleyin.


5

JUnique kitaplığını kullanabilirsiniz. Tek örnekli java uygulamasını çalıştırmak için destek sağlar ve açık kaynaklıdır.

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

JUnique kitaplığı, bir kullanıcının aynı anda aynı Java uygulamasının daha fazla örneğini çalıştırmasını önlemek için kullanılabilir.

JUnique, aynı kullanıcı tarafından başlatılan tüm JVM örnekleri arasında paylaşılan kilitleri ve iletişim kanallarını uygular.

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

Başlık altında,% USER_DATA% /. Junique klasöründe dosya kilitleri oluşturur ve java uygulamaları arasında mesaj göndermeye / almaya izin veren her benzersiz uygulama kimliği için rastgele bağlantı noktasında bir sunucu soketi oluşturur.


Bunu, bir ağdaki java uygulamasının çoklu örneğini önlemek için kullanabilir miyim? diğer bir deyişle, tüm
ağımda başvurumun



2

Tercihler API'sini kullanmayı deneyebilirsiniz. Platformdan bağımsızdır.


API basit olduğu için bu fikri seviyorum, ancak belki bazı virüs tarayıcıları kayıt defterini değiştirmenizi istemez, bu nedenle yazılım güvenlik duvarı olan sistemlerde RMI kullanımıyla benzer sorunlar yaşarsınız .... emin değilim.
Cal

@Cal Ama aynı sorun dosya değiştirme / kilitleme / vb ... ile ilgili değil mi?
Alex

2

Tek bir makinede veya hatta tüm ağda örnek sayısını sınırlamanın daha genel bir yolu, çok noktaya yayın soketi kullanmaktır.

Çok noktaya yayın soketinin kullanılması, uygulamanızın herhangi bir sayıda örneğine mesaj yayınlamanıza olanak tanır; bunlardan bazıları kurumsal bir ağ üzerindeki fiziksel olarak uzak makinelerde olabilir.

Bu şekilde, aşağıdaki gibi şeyleri kontrol etmek için birçok konfigürasyon türünü etkinleştirebilirsiniz.

  • Makine başına bir veya birçok örnek
  • Ağ başına bir veya daha fazla örnek (ör. Bir istemci sitesinde kurulumları kontrol etme)

Java'nın çok noktaya yayın desteği, java.net paketi aracılığıyla sağlanır MulticastSocket ve DatagramSocket ile ana araçlar .

Not : MulticastSocket'ler veri paketlerinin teslimini garanti etmez, bu nedenle JGroups gibi çok noktaya yayın soketleri üzerine kurulu bir araç kullanmalısınız . JGroups , tüm verilerin teslimini garanti eder. Çok basit bir API'ye sahip tek bir jar dosyasıdır.

JGroups bir süredir ortalıktaydı ve endüstride bazı etkileyici kullanımları var, örneğin JBoss'un kümeleme mekanizmasının temelini oluşturuyor ve bir kümenin tüm örneklerine veri yayınlıyor.

JGroups kullanmak, bir uygulamanın örnek sayısını sınırlamak (bir makinede veya bir ağda, diyelim ki: bir müşterinin satın aldığı lisansların sayısı ile) kavramsal olarak çok basittir:

  • Uygulamanızın başlangıcında, her bir örnek "Benim Harika Uygulama Grubum" gibi adlandırılmış bir gruba katılmaya çalışır. Bu grubu 0, 1 veya N üyeye izin verecek şekilde yapılandırmış olacaksınız
  • Grup üye sayısı, onun için yapılandırdığınızdan daha yüksek olduğunda .. uygulamanız başlatmayı reddetmelidir.

1

Bir Bellek Eşlemeli Dosyayı açabilir ve ardından bu dosyanın zaten AÇIK olup olmadığını görebilirsiniz. zaten açıksa, ana sayfadan dönebilirsiniz.

Diğer yollar kilit dosyalarını kullanmaktır (standart unix uygulaması). Bir başka yol da, panoda zaten bir şey olup olmadığını kontrol ettikten sonra ana başladığında panoya bir şey koymaktır.

Aksi takdirde, dinleme modunda (ServerSocket) bir soket açabilirsiniz. Önce hte soketine bağlanmayı deneyin; Bağlanamıyorsanız, bir serverocket açın. bağlanırsanız, başka bir örneğin zaten çalışmakta olduğunu bilirsiniz.

Dolayısıyla, hemen hemen her sistem kaynağı, bir uygulamanın çalıştığını bilmek için kullanılabilir.

BR, ~ A


bu fikirlerden herhangi biri için kodunuz var mı? ayrıca, kullanıcı yeni bir örnek başlatırsa öncekilerin tümünü kapatmasını istersem ne olur?
android geliştiricisi

1

Bunun için soketler kullandım ve uygulamanın istemci tarafında veya sunucu tarafında olmasına bağlı olarak davranış biraz farklı:

  • istemci tarafı: bir örnek zaten mevcutsa (belirli bir bağlantı noktasında dinleyemiyorum) uygulama parametrelerini geçeceğim ve çıkacağım (önceki örnekte bazı eylemler gerçekleştirmek isteyebilirsiniz) yoksa uygulamayı başlatacağım.
  • sunucu tarafı: bir örnek zaten mevcutsa bir mesaj yazdıracağım ve çıkacağım, yoksa uygulamayı başlatacağım.

1
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";
    özel statik JFrame çerçevesi = boş;

    public static void main (String [] args) {
        Deneyin {
            FileChannel lockChannel = new RandomAccessFile (LOCK, "rw"). GetChannel ();
            FileLock flk = null; 
            Deneyin {
                flk = lockChannel.tryLock ();
            } catch (Fırlatılabilir t) {
                t.printStackTrace ();
            }
            eğer (flk == null ||! flk.isValid ()) {
                System.out.println ("zaten çalışıyor, boruya mesaj bırakıp çıkılıyor ...");
                FileChannel pipeChannel = null;
                Deneyin {
                    pipeChannel = new RandomAccessFile (PIPE, "rw"). getChannel ();
                    MappedByteBuffer bb = pipeChannel.map (FileChannel.MapMode.READ_WRITE, 0, 1);
                    bb.put (0, (bayt) 1);
                    bb.force ();
                } catch (Fırlatılabilir t) {
                    t.printStackTrace ();
                } en sonunda {
                    eğer (pipeChannel! = null) {
                        Deneyin {
                            pipeChannel.close ();
                        } catch (Fırlatılabilir t) {
                            t.printStackTrace ();
                        }
                    } 
                }
                System.exit (0);
            }
            // Burada kilidi açıp kanalı kapatmayız, 
            // uygulama çöktükten veya normal şekilde kapandıktan sonra yapılacak. 
            SwingUtilities.invokeLater (
                new Runnable () {
                    public void run () {
                        createAndShowGUI ();
                    }
                }
            );

            FileChannel pipeChannel = null;
            Deneyin {
                pipeChannel = new RandomAccessFile (PIPE, "rw"). getChannel ();
                MappedByteBuffer bb = pipeChannel.map (FileChannel.MapMode.READ_WRITE, 0, 1);
                while (true) {
                    bayt b = bb.get (0);
                    eğer (b> 0) {
                        bb.put (0, (bayt) 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 (Fırlatılabilir t) {
                t.printStackTrace ();
            } en sonunda {
                eğer (pipeChannel! = null) {
                    Deneyin {
                        pipeChannel.close ();
                    } catch (Fırlatılabilir t) {
                        t.printStackTrace ();
                    } 
                } 
            }
        } catch (Fırlatılabilir t) {
            t.printStackTrace ();
        } 
    }

    public static void createAndShowGUI () {

        çerçeve = yeni JFrame ();
        frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
        frame.setSize (800, 650);
        frame.getContentPane (). add (yeni JLabel ("ANA PENCERE", 
                    SwingConstants.CENTER), BorderLayout.CENTER);
        frame.setLocationRelativeTo (null);
        frame.setVisible (true);
    }
}


1

DÜZENLEME : Bu WatchService yaklaşımını kullanmak yerine, 1 saniyelik basit bir zamanlayıcı iş parçacığı, göstergeFile.exists () olup olmadığını kontrol etmek için kullanılabilir. Silin, ardından uygulamayı Front () konumuna getirin.

DÜZENLEME : Bunun neden reddedildiğini bilmek istiyorum. Şimdiye kadar gördüğüm en iyi çözüm bu. Örneğin, başka bir uygulama bağlantı noktasını dinliyorsa, sunucu soket yaklaşımı başarısız olur.

Sadece Microsoft Windows Sysinternals TCPView'ı indirin (veya netstat kullanın), başlatın, " Duruma " göre sıralayın, "DİNLEME" yazan satır bloğunu arayın, uzak adresi bilgisayarınızın adını yazan birini seçin ve bu bağlantı noktasını yeni Soketinize yerleştirin ()-çözüm. Bunu uygulamamda her seferinde başarısızlık üretebilirim. Ve mantıklı çünkü yaklaşımın temelini oluşturuyor. Ya da bunun nasıl uygulanacağına dair neyi anlamıyorum?

Lütfen bana bu konuda yanıldığımı ve nasıl yanıldığımı bildirin!

Benim görüşüm - mümkünse çürütmenizi istediğim - geliştiricilere üretim kodunda yaklaşık 60000 vakanın en az 1'inde başarısız olacak bir yaklaşım kullanmaları tavsiye ediliyor. Ve eğer bu görüş doğruysa, o zaman bu soruna sahip olmayan bir çözümün kod miktarı nedeniyle reddedilmesi ve eleştirilmesi kesinlikle olamaz .

Soket yaklaşımının karşılaştırmalı dezavantajları:

  • Yanlış piyango bileti (port numarası) seçilirse başarısız olur.
  • Çok kullanıcılı ortamda başarısız olur: Uygulamayı aynı anda yalnızca bir kullanıcı çalıştırabilir. (Kullanıcı ağacında dosya (lar) oluşturmak için yaklaşımımın biraz değiştirilmesi gerekecekti, ama bu önemsiz.)
  • Güvenlik duvarı kuralları çok katı ise başarısız olur.
  • Şüpheli kullanıcıları (vahşi doğada tanıştım) metin düzenleyiciniz bir sunucu soketi talep ettiğinde ne tür maskaralıklarla uğraştığınızı merak ediyor.

Yeni örnek-var olan Java iletişim problemini her sistemde çalışması gereken bir şekilde nasıl çözeceğime dair güzel bir fikrim var. Yani, bu dersi yaklaşık iki saat içinde hazırladım. Cazibe gibi çalışır: D

Robert'in o zamandan beri kullandığım dosya kilitleme yaklaşımına (bu sayfada da) dayanmaktadır . Zaten çalışan örneğe başka bir örneğin başlamayı denediğini (ancak başlamadığını) söylemek için ... bir dosya oluşturulur ve hemen silinir ve ilk örnek, bu klasör içeriği değişikliğini algılamak için WatchService'i kullanır. Sorunun ne kadar temel olduğu düşünüldüğünde, görünüşe göre bunun yeni bir fikir olduğuna inanamıyorum.

Bu , dosyayı yalnızca oluşturmak ve silmemek için kolayca değiştirilebilir ve daha sonra, uygun örneğin değerlendirebileceği bilgiler, örneğin komut satırı argümanları - ve uygun örnek daha sonra silme işlemini gerçekleştirebilir. Şahsen, sadece uygulamamın penceresini ne zaman geri yükleyeceğimi ve onu öne göndermem gerektiğini bilmem gerekiyordu.

Örnek kullanım:

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.");
}

İşte sınıf:

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

}

Bu sorunu çözmek için yüzlerce satır koda ihtiyacınız yoktur. new ServerSocket()bir catch bloğu ile oldukça yeterli,
user207421

@EJP Kabul edilen cevaba mı atıfta bulunuyorsunuz yoksa neden bahsediyorsunuz? X-platform ekstra kitaplık içermeyen bir çözüm için epeyce araştırma yaptım, örneğin bazı soketler zaten farklı bir uygulama tarafından kullanıldığı için başarısız olmaz . Bunun bir çözümü varsa - özellikle de bahsettiğiniz kadar basit - o zaman bunu bilmek isterim.
Dreamspace Başkan

@EJP: Tekrar sormak istiyorum 1) kafamın önünde bir havuç gibi sallanıp durduğunuza atıfta bulunduğunuz ne önemsiz bir çözümden bahsediyorsunuz, 2) kabul edilen cevabın başladığı soket çözümü olması durumunda, benim "yuva yaklaşımının dezavantajları" madde işaretleri geçerlidir ve 3) öyleyse, neden bu eksikliklere rağmen benimki gibi bir yaklaşımı tavsiye edersiniz?
Dreamspace Başkan

@EJP: Sorun şu ki, emin olduğunuz gibi, sesinizin oldukça ağırlığı var, ancak sahip olduğum tüm kanıtlar beni buradaki tavsiyenizin yanlış olduğuna ikna etmeye zorluyor . Bakın, çözümümün doğru olduğu konusunda ısrar etmiyorum ama kanıta dayalı bir makineyim. Eğer pozisyon size verdiğini görmüyor musunuz sorumluluk bu iletişimin eksik yapboz parçaları doldurmak için topluma sen başladı?
Dreamspace Başkanı

@EJP: Maalesef sizden hiçbir tepki gelmediğine göre, işte gerçek olarak varsayacağım: Sunucu soket çözümüyle ilgili gerçek şu ki, gerçekten derinden kusurlu ve onu seçenlerin çoğunun nedeni "Diğerleri bunu da kullanın. "yoksa sorumsuz kişiler tarafından onu kullanmak için aldatılmış olabilirler. Ben tahmin gerekli açıklamalar bizi tenezzül etmedi neden sebeple o kısmı size bu yaklaşımı sorgulandığı bu yüzden hiç / anlayamaz ve bu açıklamadan bir kamu açıklama yapmak istemiyorum olmasıdır.
Dreamspace President

1

Unique4j kitaplığı, bir Java uygulamasının tek bir örneğini çalıştırmak ve mesajları geçirmek için kullanılabilir. Bunu https://github.com/prat-man/unique4j adresinde görebilirsiniz . Java 1.6 + 'yı destekler.

Yalnızca bir örneğin çalışmasına izin verme birincil amacı ile örnekleri algılamak ve bunlar arasında iletişim kurmak için dosya kilitleri ve dinamik bağlantı noktası kilitlerinin bir kombinasyonunu kullanır.

Aşağıda bunun basit bir örneği verilmiştir:

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();

    }

}

Sorumluluk Reddi: Unique4j kitaplığını oluşturdum ve sürdürüyorum.


1

Bunun için özel bir kitaplık yazdım https://sanyarnd.github.io/applocker

Dosya-kanal kilitlemesine dayanır, bu nedenle bir bağlantı noktası numarasını veya elektrik kesintisi durumunda kilitlenme uygulamasını engellemez (işlem sonlandırıldığında kanal serbest bırakılır).

Kitaplığın kendisi hafiftir ve akıcı bir API'ye sahiptir.

Http://www.sauronsoftware.it/projects/junique/'den esinlenmiştir , ancak bunun yerine dosya kanallarına dayanmaktadır. Ve başka ekstra yeni özellikler de var.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.