java - 在 Java 中使用计划执行器而不是硬循环来监视目录

标签 java multithreading nonblocking executorservice watch

在我的previous question我正在做一个简单的练习,观察目录中的文件更改。我从this oracle docs获取代码,并且它的工作没有任何问题,除了我不确定的未经检查的小 Actor 警告。

我对这段代码遇到的下一个问题是,它在线程内放置了一个硬循环,该循环是阻塞的,至少在理论上是这样。现在,我知道,如果操作系统使用时间切片,甚至硬循环也会被分成小块,这些小块与应用程序正在运行的其他线程共享处理器的时间,事实上,我可以制作各种硬循环的示例在不同线程中运行不会互相阻塞(只要它们具有相同的优先级),即使在专门创建的只有一个核心的虚拟机上也是如此。

然而,Java 语言并不能保证它使用哪种调度来进行线程管理,是时间片调度还是循环调度;这取决于实际的VM实现和操作系统。因此,我在研究该主题时得到的建议是编写代码,就好像它必须在循环线程调度上运行一样,从而避免在线程中放置硬循环,除非我的代码可以连续地将控制权交还给其他线程与 sleep()wait()yeld() 等(我可以想到一个 GUI,其中主线程是使用硬循环监视事件,并将控制发送回监听器来处理它们)。

但是,就我而言,我想不出一种方法可以让线程在处理文件更改后进入休眠状态,或者将控制权交还给主循环,因为核心思想基本上是不断询问文件系统是否有更改。所以我想到的是一个定期调用监视线程的预定执行器。显然,这是拥有“阻塞”线程和在文件系统发生更改时立即收到通知之间的权衡。由于在实际情况中我将进行此练习,因此我可能不需要立即通知,我对此感到满意。代码非常简单:

// imports...
public class Main
{
    public static FileSystem fs;
    public static Path dir;
    public static WatchService watcher;
    public static WatchKey key;

    public static void main(String[] args)
    {
        fs = FileSystem.getDefault();
        dir = fs.getPath(".");

        try {
            watcher = fs.newWatchService();
            dir.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);
        } catch (IOException e) {
            System.err.println(e.getMessage());
            return;
        }

        Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable()
            {
                public void run()
                {
                    Main.key = Main.watcher.poll();
                    if (null == Main.key)
                        return;

                    for (WatchEvent<?> event : Main.key.pollEvents()) {
                        WatchEvent.Kind<?> kind = event.kind();
                        if (kind == StandardWatchEventKinds.OVERFLOW)
                            continue;

                        @SuppressWarnings("unchecked");
                        WatchEvent<Path> ev = (WatchEvent<Path>)event;
                        Path file = ev.context();
                        System.out.println(file);

                        boolean valid = Main.key.reset();
                        if (!valid)
                            System.err.println("Invalid key!");
                    }
                }
            }, 0, 1, TimeUnit.SECONDS);
    }
}

所以我的问题是:

  1. 我是否太过分了?我的意思是,如此关心线程中的阻塞代码实际上是一个好习惯吗?或者是不存在时间切片的真实情况,如此罕见,以至于我可以安全地在线程内放置一个硬循环,也许可以这样做仅当我知道我的代码可能会在保证循环的嵌入式设备上运行时才执行此类操作?

  2. 在这种特殊情况下还有其他方法可以避免硬循环吗?也许我想不到一些巧妙的线程控制方法(sleep()wait()等)的使用?

非常感谢您,对这么长的帖子表示歉意。

最佳答案

这是一个如何在后台线程中监视目录的示例。修改后的Java Tutorials Code Sample – WatchDir.java引用者Oracle's The Java Tutorials: Watching a Directory for Changes .

重要的部分是watcher.take()。这里,调用线程会阻​​塞,直到发出按键信号为止。因此,这些是这种方法与代码片段相反的好处:

  1. 线程在 watcher.take() 中等待时被“停放”。等待时不会浪费 CPU 周期/资源。 CPU 可以同时做其他事情。
  2. watcher.take() 当文件系统被修改时立即返回。 (在最坏的情况下,您的代码会在 1 秒后使用react,在一般情况下会在 0.5 秒后使用react。)

在 main 方法中,DirWatcher 由单线程 ExecutorService 实例化并运行。此示例在关闭观察程序和执行程序服务之前等待 10 秒。

public class DirWatcher implements Runnable {

    private final Path dir;
    private final WatchService watcher;
    private final WatchKey key;

    @SuppressWarnings("unchecked")
    static <T> WatchEvent<T> cast(WatchEvent<?> event) {
        return (WatchEvent<T>) event;
    }

    /**
     * Creates a WatchService and registers the given directory
     */
    public DirWatcher(Path dir) throws IOException {
        this.dir = dir;
        this.watcher = FileSystems.getDefault().newWatchService();
        this.key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
    }

    public void run() {
        try {
            for (;;) {
                // wait for key to be signalled
                WatchKey key = watcher.take();

                if (this.key != key) {
                    System.err.println("WatchKey not recognized!");
                    continue;
                }

                for (WatchEvent<?> event : key.pollEvents()) {
                    WatchEvent<Path> ev = cast(event);
                    System.out.format("%s: %s\n", ev.kind(), dir.resolve(ev.context()));
                    // TODO: handle event. E.g. call listeners
                }

                // reset key
                if (!key.reset()) {
                    break;
                }
            }
        } catch (InterruptedException x) {
            return;
        }
    }

    public static void main(String[] args) throws IOException, InterruptedException, ExecutionException,
            TimeoutException {

        Path dir = Paths.get("C:\\temp");
        DirWatcher watcher = new DirWatcher(dir);

        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<?> future = executor.submit(watcher);
        executor.shutdown();

        // Now, the watcher runs in parallel
        // Do other stuff here

        // Shutdown after 10 seconds
        executor.awaitTermination(10, TimeUnit.SECONDS);
        // abort watcher
        future.cancel(true);

        executor.awaitTermination(1, TimeUnit.SECONDS);
        executor.shutdownNow();
    }
}

关于java - 在 Java 中使用计划执行器而不是硬循环来监视目录,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30284302/

相关文章:

java - Thread.sleep(0) 和 Thread.yield() 语句是否等效?

zeromq - ZeroMQ模式,用于基于空闲状态在工作人员之间进行负载均衡工作

java - Spring Security 过滤器阻止所有 Hibernate 查询

java - 如何检查哪些点在圆内?

java - 有没有办法在给定字段名的情况下获取对象中某个字段的值?

c - 有没有办法在 POSIX C 中做到这一点?

verilog - 具有内部分配延迟的阻塞语句和非阻塞语句之间的区别

java - 如何在通过java创建的Excel工作表中设置整数值?

java - 最新的 JMM 是否指定同步块(synchronized block)对于其他线程(甚至异步线程)是原子的?

.net - .net:线程调试