2008-10-07 81 views
78

有時我看到許多應用程序,如單個實例應用程序(如用戶在運行應用程序時執行的應用程序不會創建新應用程序實例的情況下,如msn,windows media player等)。如何實現單個實例Java應用程序?

在C#中,我使用Mutex類,但我不知道如何在Java中做到這一點。

+0

一個非常簡單的方法與Java NIO看完整的例子http://stackoverflow.com/a/20015771/185022 – 2013-11-16 07:03:43

回答

56

如果我相信這article,通過:

具有一審試圖打開本地主機接口上的監聽套接字。如果它能夠打開套接字,則假定這是要啓動的應用程序的第一個實例。如果不是,則假設該應用程序的一個實例已經在運行。新實例必須通知現有實例啓動嘗試,然後退出。現有實例在接收到通知後接管並將事件觸發到處理該操作的偵聽器。

注:在評論Ahe提到使用InetAddress.getLocalHost()可能會非常棘手:

  • 如預期在DHCP環境中這是行不通的,因爲地址返回取決於計算機是否具有網絡訪問。
    解決方案是打開連接InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
    可能與bug 4435662相關。
  • 我還發現bug 4665037這比getLocalHost預期結果報告:機返回的IP地址,與實際結果:返回127.0.0.1

人們驚奇地在Linux上,而不是在Windows getLocalHost回報127.0.0.1


或者你可以使用ManagementFactory對象。如所解釋的here

getMonitoredVMs(int processPid)方法接收作爲參數的當前應用的PID,並捕捉是從命令行調用,例如,該應用程序是從c:\java\app\test.jar路徑啓動的應用程序的名稱,則該值變量爲「 c:\\java\\app\\test.jar「。這樣,我們將在下面的代碼的第17行捕獲應用程序名稱。
之後,我們搜索JVM以查找具有相同名稱的另一個進程,如果我們發現它並且應用程序PID不同,則表示這是第二個應用程序實例。

JNLP還提供了SingleInstanceListener

+3

請注意,拳頭的解決方案有一個錯誤。最近我們發現`InetAddress.getLocalHost()`在DHCP環境中不能按預期工作,因爲返回的地址取決於計算機是否具有網絡訪問權限。解決方法是用`InetAddress.getByAddress(new byte [] {127,0,0,1});`打開連接。 – Ahe 2010-09-17 08:31:28

+2

@Ahe:優點。我在編輯的答案中包含了您的評論以及Oracle-Sun錯誤報告參考。 – VonC 2010-09-17 08:41:42

+2

根據JavaDoc'InetAddress.getByName(null)`返回回送接口的地址。我想這是更好的,然後手動指定127.0.0.1,因爲從理論上講,這應該也適用於純IPv6環境。 – kayahr 2012-02-09 08:55:48

5

我們使用文件鎖定這個(搶在用戶的應用數據目錄中的神奇文件的獨佔鎖定),但我們在防止多個實例從以往運行的主要興趣。

如果您試圖讓第二個實例將命令行參數等傳遞給第一個實例,那麼在本地主機上使用套接字連接將會一石二鳥。一般算法:

  • 上啓動,嘗試在端口XXXX打開監聽localhost上
  • 如果失敗了,打開一個作家在本地主機端口和發送命令行參數,然後關機
  • 否則,聽在本地主機上的端口XXXXX上。當收到命令行參數時,就像處理該命令行啓動應用程序一樣處理它們。
1

您可以打開一個內存映射文件,然後查看該文件是否已經打開。如果它已經打開,你可以從main返回。

其他的方法是使用鎖定文件(標準的unix練習)。還有一種方法是在檢查剪貼板中是否存在內容後,在main啓動時將某些內容放入剪貼板。

否則,您可以在偵聽模式下打開套接字(ServerSocket)。首先嚐試連接到hte套接字;如果你不能連接,那麼打開一個serversocket。如果你連接,那麼你知道另一個實例已經在運行。

因此,幾乎所有的系統資源都可以用於瞭解應用程序正在運行。

BR, 〜或J2SE 5.0支持的

2

您可以嘗試使用Preferences API。它是平臺獨立的。

4

在Windows上,您可以使用launch4j

1

我用的是插座和根據,如果應用程序是在客戶端或服務器端的行爲是有點不同:

  • 客戶端:如果一個實例已經存在(我不能在一個特定的聽端口)我將傳遞應用程序參數並退出(您可能想在前一個實例中執行一些操作),否則我將啓動應用程序。
  • 服務器端:如果一個實例已經存在,我將打印一條消息並退出,如果沒有,我將啓動該應用程序。
46

我在主要方法中使用以下方法。這是我見過的最簡單,最健壯,最不干擾的方法,所以我想我會分享它。

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

是的,這是一個Eclipse RCP一個真正像樣的答案日食低於單實例應用程序 是我的代碼

在application.java

if(!isFileshipAlreadyRunning()){ 
     MessageDialog.openError(display.getActiveShell(), "Fileship already running", "Another instance of this application is already running. Exiting."); 
     return IApplication.EXIT_OK; 
    } 


private static boolean isFileshipAlreadyRunning() { 
    // socket concept is shown at http://www.rbgrn.net/content/43-java-single-application-instance 
    // but this one is really great 
    try { 
     final File file = new File("FileshipReserved.txt"); 
     final RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); 
     final FileLock fileLock = randomAccessFile.getChannel().tryLock(); 
     if (fileLock != null) { 
      Runtime.getRuntime().addShutdownHook(new Thread() { 
       public void run() { 
        try { 
         fileLock.release(); 
         randomAccessFile.close(); 
         file.delete(); 
        } catch (Exception e) { 
         //log.error("Unable to remove lock file: " + lockFile, e); 
        } 
       } 
      }); 
      return true; 
     } 
    } catch (Exception e) { 
     // log.error("Unable to create and/or lock file: " + lockFile, e); 
    } 
    return false; 
} 
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"; 
    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); 
    } 
} 

0

EDIT:除了使用此WatchService方法的,簡單的1秒計時器線程可用於檢查indicatorFile.exists()。刪除它,然後將應用程序帶到Front()。

編輯:我想知道爲什麼這是downvoted。這是迄今爲止我所見過的最好的解決方案。例如。如果另一個應用程序碰巧已經在監聽端口,服務器套接字方法將失敗。 (或使用netstat),啓動它,按「狀態」排序,尋找「LISTENING」的行塊,選擇一個遠程地址表示您的計算機名稱,將該端口放入你的新的Socket()解決方案。在我的實施中,每次都會產生失敗。它是邏輯,因爲它是該方法的基礎。或者我沒有得到關於如何實現這個的?

請告訴我是否以及如何我錯了!

我的看法 - 我要求你反駁如果可能的話 - 是建議開發人員在生產代碼中使用一種方法,在大約60000個案例中至少有一個會失敗。如果這個觀點恰好是正確的,那麼它絕對可以是而不是是因爲沒有這個問題的解決方案因其代碼量而被低估和批評。在比較

缺點插座方法:如果選擇了錯誤的彩票(端口號)

  • 失敗。
  • 多用戶環境中失敗:只有一個用戶可以同時運行該應用程序。 (我的方法必須稍微修改才能在用戶樹中創建文件,但這很簡單。)
  • 如果防火牆規則太嚴格,則失敗。
  • 使可疑的用戶(我在野外遇到過)想知道當你的文本編輯器聲稱服務器套接字時你要做什麼。

我不得不爲如何的方式,應該在每個系統上工作,解決新實例到現有實例的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(); 
     } 
    } 

} 
2

限制性實例的數目的更通用的方法是在單個機器,或甚至整個網絡上,是使用組播套接字。

使用多路廣播套接字,可以將消息廣播到任意數量的應用程序實例,其中一些實例可以位於企業網絡中的物理遠程計算機上。

這樣就可以使許多類型的配置,以控制之類的每臺機器的每個網絡

  • 一個或多個實例(在客戶現場例如控制安裝)
    • 一個或多個實例

    Java的組播支持是通過java.net包MulticastSocket時 & DatagramSocket是主要的工具。

    注意:MulticastSocket不能保證傳送數據包,所以你應該使用一個構建在多播套接字之上的工具,如JGroups。 JGroups 確實保證傳遞所有數據。它是一個單獨的jar文件,具有非常簡單的API。

    JGroups已經有一段時間了,在業界有一些令人印象深刻的用法,例如它支持JBoss的集羣機制向所有集羣實例廣播數據。

    要使用的JGroups,以限制應用程序的實例(一臺機器或網絡上)的數量是概念上非常簡單:

    • 在你的應用程序啓動時,每個實例都試圖加入一個名爲組例如「我的偉大應用程序組」。您將配置該組以允許0,1或N個成員
    • 當組成員計數大於您爲其配置的值時,您的應用應該拒絕啓動。
    4

    您可以使用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文件夾中創建文件鎖,並創建在隨機端口爲每個唯一的appid,允許發送/接收的Java應用程序之間的消息的服務器套接字。