Поочередная запись потоками в файл

Всем привет, есть задание:
На вход передается целое число, больше 0, кратное 2 (n). Создается два потока, работающих параллельно, каждый поток в цикле, считывает значение из файла out.txt увеличивает его на 1, выводит в консоли старое значение, новое значение и идентификатор потока (идентификатор может быть произвольным), и записывает обратно в файл. В конечном итоге оба потока должны успешно завершить свою работу, и в консоль должно вывестись содержимое файла out.txt (которое должно быть равно заданному на входе n).
Пытаюсь сделать так:

private static final Semaphore semaphore = new Semaphore(1);

public void execute() {
    threadOne.start();
    threadTwo.start();
} 

private void task() {
    try {
        semaphore.acquire();
        while (incrementValue()) {
            incrementValue();
            semaphore.release();
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}


private boolean incrementValue() {
    try {
        Scanner reader = new Scanner(new FileReader(FILE_NAME));
        int value = Integer.parseInt(reader.nextLine());
        if (value == n) {
            return false;
        }
        FileWriter writer = new FileWriter(FILE_NAME);
        writer.write(String.valueOf(value + 1));
        System.out.printf("Thread: %s, old - %d, new - %d", Thread.currentThread().getName(), value, ++value);
        System.out.println();
        writer.flush();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return true;
}

В итоге на выходе получается это:

Thread: Thread-0, old - 0, new - 1
Thread: Thread-0, old - 1, new - 2
Thread: Thread-0, old - 2, new - 3
Thread: Thread-1, old - 2, new - 3
Thread: Thread-0, old - 3, new - 4
Thread: Thread-1, old - 3, new - 4

Как сделать так, чтобы потоки поочередно записывали в файл? Заранее благодарю за помощь.


Ответы (1 шт):

Автор решения: Roman Konoval

Сразу целая куча проблем:

  1. семафор захватываете перед циклом, а отпускаете внутри. После первой итерации семафор отпущен, а поток продолжает себе работать с файлом. Нужно захватывать один раз в начале тела цикла.
  2. За одну итерацию метод incrementValue вызывается два раза. Сначала в условии цикла while и еще раз в теле. Делайте раз в теле и присваивайте переменной, которую и проверяйте в условии.
  3. Файлу после записи нужно не просто flush делать, а полностью закрывать. Пользуйтесь try-with-resources

Вот так (плюс изменения по работе с файлами) работает лучше:

boolean toContinue = true;
while (toContinue) {
    semaphore.acquire();
    toContinue = incrementValue();
    semaphore.release();
    Thread.sleep(1);  // см. пояснения ниже 
}

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

→ Ссылка