2015-05-17 3 views
0

В моем previous question я был на простом упражнении, наблюдая за каталогом изменений файла. Я взял код от this oracle docs, и он работал без проблем, за исключением небольшого неконтролируемого предупреждения о броске, о котором я не знал.Смотреть каталог, используя запланированный исполнитель вместо жесткой петли в Java

Следующая проблема с этим кодом заключается в том, что он помещает жесткий цикл внутри потока, который блокирует, по крайней мере теоретически. Теперь я знаю, что если операционная система использует временную разбивку, даже жесткий цикл разделяется небольшими фрагментами, которые разделяют время процессора с другими потоками приложения, и на самом деле я могу делать всевозможные примеры, где жесткие циклы работающие в разных потоках, не блокируют друг друга (если они имеют одинаковый приоритет), даже на виртуальной машине, явно созданной только с одним ядром.

Однако язык Java не гарантирует, какой вид планирования он использует для управления потоками, если это временная нарезка или циклическая обработка; это зависит от реальной реализации VM и операционной системы. Поэтому совет, который я получаю при изучении предмета, заключается в написании кода так, как если бы он выполнялся при планировании потоков с циклическим циклом, таким образом избегая помещать жесткие петли в потоки, если только мой код не может постоянно отводить контроль над другими потоками с sleep(), wait(), yeld() и т. д. (я могу думать о графическом интерфейсе, где основной поток - это тот, где жесткий цикл наблюдает за событиями, и отправляет управление обратно слушателям для их обработки).

В моем случае, однако, я не мог придумать способ скрыть поток после того, как он обработает изменение файла, или вернуть управление в основной цикл, поскольку основная идея в основном заключается в непрерывном спросите, есть ли изменения в файловой системе. Итак, к чему я пришел, это запланированный исполнитель, который регулярно называет наблюдательный поток. Ясно, что это компромисс между наличием «блокирующего» потока и немедленным уведомлением о произошедшем изменении файловой системы. Поскольку в реальном случае я собираюсь включить это упражнение, мне, вероятно, не понадобится немедленное уведомление, я доволен этим. Код очень прост:

// 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() и т. Д.), О которых я не могу думать?

Большое спасибо и извините за длинный пост.

+2

Почему вы используете 'poll()'? 'take()' позволит избежать потери процессора. –

+0

@RogerGustavsson Если я правильно понял документы, 'take()' (который я использовал в предыдущей версии этого кода) блокирует выполнение, ожидающее следующего ключа Watch, в то время как 'poll()' выталкивает следующие часы ключ или «null», если ни один не присутствует, и в любом случае немедленно возвращается. Поэтому кажется, что 'poll()' - это способ избежать ожидания. – swahnee

+0

@swahnee Почему вы хотите избежать ожидания? Я думал, что это именно то, что вы хотите: пусть ваш поток ждет, пока не появится новый ключ часов. – isnot2bad

ответ

0

Как было отмечено в комментариях, метод take() не блокирует выполнение потока до тех пор новый ключ предоставляется, но она использует механизм, аналогичный методу wait() положить нить спать.

Я также нашел this post, где указано, что WatcherService использует механизм уведомления о событиях на родном файле, если имеется (за исключением the OSX implementation).

Итак, чтобы ответить на мой собственный вопрос, использование take() внутри жесткой петли не блокирует поток, поскольку он автоматически помещает поток в режим сна, позволяя другим потокам использовать процессор, пока уведомление об изменении файла не поступит от операционной системы система.

2

Вот пример того, как вы можете смотреть каталог в фоновом потоке. Это модифицированный номер Java Tutorials Code Sample – WatchDir.java, на который ссылается Oracle's The Java Tutorials: Watching a Directory for Changes.

Важная часть watcher.take(). Здесь вызывающий поток блокируется до тех пор, пока не будет сигнализирован ключ. Таким образом, это преимущества этого подхода в противоположность вашему фрагменту кода:

  1. Нить «припаркована» в ожидании в watcher.take(). Никакие циклы/ресурсы процессора не теряются во время ожидания. Тем временем CPU может делать другие вещи.
  2. watcher.take() немедленно возвращается при изменении файловой системы. (Ваш код реагирует через 1 секунду в худшем случае, и после 0,5 секунды в среднем случае.)

В основном методе 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(); 
    } 
} 
+0

Спасибо за ваше предложение. Мое недоразумение было связано с методом 'take()', но теперь я вижу, что он создает поток 'wait()'. – swahnee

+0

Не сказано, что поток действительно вызывает 'wait()'. Но на самом деле он делает что-то очень похоже на 'wait()'. – isnot2bad

+0

Спасибо за предложение, это помогло. Однако я хотел бы показать, что можно остановить наблюдател _gently_ путем отмены ключа и закрытия Бодрствующего .: 'частный недействительным stopWatchService() { \t если (ключ! = NULL) { \t \t ключ.Отмена(); \t \t} \t \t если (наблюдающий! = NULL) { \t \t \t watcher.close(); \t \t} \t} ' – socona