Надежная запись из нескольких потоков в один PrintWriter

#java #multithreading #file-io #io

#java #многопоточность #file-io #io

Вопрос:

Я столкнулся с проблемой, когда у меня есть несколько потоков, которые записывают в один и тот же PrintWriter, и не все данные записываются в файл. Я знаю, что многопоточная часть работает правильно, поскольку я могу распечатать все на консоль. Синхронизация операторов записи, похоже, не работает. В чем может быть проблема?

 ExecutorService pool = Executors.newFixedThreadPool(poolSize);

for (Integer i : map.keySet()) {
    final Collection<String[]> set = map.get(i);
    pool.submit(new Runnable() {
        public void run() {
        StringBuffer sb = Matcher.performCollectionMatch(params);
        synchronized (this) {
            resultFile.print(sb); //this is a PrintWriter - it does NOT capture all sb
            resultFile.flush();
            System.out.print(sb); //this actually prints out ALL sb
        }
        }
    });
} //FOR loop
  

Комментарии:

1. достаточно ли велик размер вашего пула?

2. Я предполагаю, что это так. Я установил его на 10 потоков, и у меня не закончилась память. Кроме того, я знаю, что потоки выполняют работу правильно, поскольку я могу получать выходные данные из System.out, и это точно.

Ответ №1:

Чтобы синхронизация работала, вы должны использовать один и тот же объект для всех потоков, например:

 ...
synchronized (resultFile) {
...
  

Комментарии:

1. Изначально это именно то, что я пробовал (мое понимание синхронизированных операторов). Однако он записал в файл еще меньше результатов.

2. Тогда что-то еще совсем не так. Этот ответ правильно исправляет синхронизацию здесь, которая фактически не защищала доступ к потоку.

Ответ №2:

Вы закрываете PrintWriter после остановки пула?

 pool.shutdown();
final boolean terminated = pool.awaitTermination(8, TimeUnit.SECONDS);
if (!terminated) {
    throw new IllegalStateException("pool shutdown timeout");
}

resultFile.close();
  

Комментарии:

1. Чего я не понимал, так это того, что pool.shutdown() не блокируется до завершения всех потоков. Простое исправление: ` pool.shutdown(); в то время как (!pool.isTerminated) { } resultFile.close(); `

2. Это while требует много ресурсов процессора. awaitTermination делает то же самое, но использует 0% процессора.

3. Спасибо за совет. Будет соответствующим образом изменено.

Ответ №3:

Более простым решением является обеспечение наличия только одного потока в пуле. Таким образом, вам не нужно синхронизировать запись, поскольку существует только один поток.

 ExecutorService pool = Executors.newSingleThreadedPool();

for (Integer i : map.keySet()) {
    final Collection<String[]> set = map.get(i);
    pool.executor(new Runnable() {
        public void run() {
            StringBuilder sb = Matcher.performCollectionMatch(params);
            resultFile.print(sb); //this is a PrintWriter - it does NOT capture all sb 
            System.out.print(sb); //this actually prints out ALL sb
        }
    });
} //FOR loop
  

Вполне вероятно, что горлышко бутылки — это ваш доступ к диску, поэтому добавление дополнительных потоков может не помочь.

Комментарии:

1. @kirdie ИМХО, цель многопоточности — максимизировать производительность, а не использовать все имеющиеся у меня ядра. Часто оптимальное количество потоков равно одному. 😉