Темы и записи файлов
У меня есть Java-программа, которая использует 20 потоков. Каждый из них записывает свои результаты в файл с именем output.txt
.
Я всегда получаю другое число строк в output.txt
.
Может ли быть проблема с синхронизацией потоков? Есть ли способ справиться с этим?
Ответы
Ответ 1
может ли это быть проблемой синхронизации потоков?
Да.
Есть ли способ справиться с этим?
Да, убедитесь, что записи сериализованы путем синхронизации на соответствующем мьютексе. Или поочередно, есть только один поток, который фактически выводит в файл, и все остальные потоки просто записывают в очередь текст, который должен быть записан в очередь, из которой извлекается нить записи. (Таким образом, 20 основных потоков не блокируются при вводе/выводе.)
Повторите мьютекс: например, , если, все они используют один и тот же экземпляр FileWriter
(или что-то еще), который я буду называть fw
, тогда они могут использовать его как мьютекс:
synchronized (fw) {
fw.write(...);
}
Если каждый из них использует свой собственный FileWriter
или что-то еще, найдите что-то еще, что все они разделяют, чтобы быть мьютексом.
Но опять же, если поток, выполняющий ввод-вывод от имени других, вероятно, также является хорошим способом.
Ответ 2
Я предлагаю вам организовать его так: один поток-потребитель будет потреблять все данные и записывать их в файл. Все рабочие потоки будут передавать данные в поток потребителя синхронно. Или при написании нескольких потоков файлов вы можете использовать некоторые реализации мьютексов или блокировок.
Ответ 3
Если вам нужно какое-либо подобие производительности и простоты управления, перейдите в очередь производителей-потребителей и только один файловый писатель, как это предложил Алекс и другие. Разрешить все потоки в файле с мьютексом просто беспорядочно - каждая задержка на диск переносится непосредственно в главные функциональные возможности приложения (с дополнительным утверждением). Это особенно непримиримо с медленными сетевыми дисками, которые, как правило, уходят без предупреждения.
Ответ 4
В этом случае вы должны использовать синхронизацию. Представьте, что 2 потока (t1 и t2) одновременно открывают файл и начинают писать на него. Изменения, выполненные первым потоком, перезаписываются вторым потоком, потому что второй поток является последним, чтобы сохранить изменения в файле. Когда поток t1 записывает в файл, t2 должен ждать, пока t1 не закончит его задачу, прежде чем он сможет его открыть.
Ответ 5
Если вы можете сохранить файл как FileOutputStream
, вы можете заблокировать его следующим образом:
FileOutputStream file = ...
....
// Thread safe version.
void write(byte[] bytes) {
try {
boolean written = false;
do {
try {
// Lock it!
FileLock lock = file.getChannel().lock();
try {
// Write the bytes.
file.write(bytes);
written = true;
} finally {
// Release the lock.
lock.release();
}
} catch ( OverlappingFileLockException ofle ) {
try {
// Wait a bit
Thread.sleep(0);
} catch (InterruptedException ex) {
throw new InterruptedIOException ("Interrupted waiting for a file lock.");
}
}
} while (!written);
} catch (IOException ex) {
log.warn("Failed to lock " + fileName, ex);
}
}
Ответ 6
Ну, без какой-либо детали реализации, это трудно понять, но, как показывает мой тестовый пример, я всегда получаю 220 строк вывода, т.е. постоянное количество строк, с FileWriter
. Обратите внимание, что здесь не используется synchronized
.
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
/**
* Working example of synchonous, competitive writing to the same file.
* @author WesternGun
*
*/
public class ThreadCompete implements Runnable {
private FileWriter writer;
private int status;
private int counter;
private boolean stop;
private String name;
public ThreadCompete(String name) {
this.name = name;
status = 0;
stop = false;
// just open the file without appending, to clear content
try {
writer = new FileWriter(new File("test.txt"), true);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
for (int i=0; i<20; i++) {
new Thread(new ThreadCompete("Thread" + i)).start();
}
}
private int generateRandom(int range) {
return (int) (Math.random() * range);
}
@Override
public void run() {
while (!stop) {
try {
writer = new FileWriter(new File("test.txt"), true);
if (status == 0) {
writer.write(this.name + ": Begin: " + counter);
writer.write(System.lineSeparator());
status ++;
} else if (status == 1) {
writer.write(this.name + ": Now we have " + counter + " books!");
writer.write(System.lineSeparator());
counter++;
if (counter > 8) {
status = 2;
}
} else if (status == 2) {
writer.write(this.name + ": End. " + counter);
writer.write(System.lineSeparator());
stop = true;
}
writer.flush();
writer.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Как я понимаю (и проверяю), в этом процессе есть две фазы:
- все потоки в пуле, все созданные и запущенные, готовы захватить файл;
- один из них захватывает его, а , я предполагаю, что он затем внутренне блокирует его, предотвращает доступ других потоков, потому что я никогда не вижу строки, объединенной из двух потоков. Поэтому, когда поток пишет, другие ждут, пока он завершит линию, и, скорее всего, выпустит файл. Итак, никаких условий гонки не произойдет.
- Самый быстрый из остальных захватывает файл и начинает писать.
Ну, это точно так же, как толпа, ожидающая снаружи ванной, без очереди...
Итак, если ваша реализация отличается, покажите код, и мы можем помочь его сломать.