2008-10-07 45 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应用程序之间的消息的服务器套接字。