Состояние гонки, возникающее при использовании параллельных потоков и атомных переменных - PullRequest
2 голосов
/ 25 апреля 2019

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

byte[][] loremIpsumContentArray = new byte[64][];

for (int i = 0; i < loremIpsumContentArray.length; i++)
{
    random.nextBytes(loremIpsumContentArray[i] = new byte[CONTENT_SIZE]);
}

AtomicBoolean aBoolean = new AtomicBoolean(true);
List<Long> resultList = IntStream.range(0, 64* 2)
                                 .parallel()
                                 .mapToObj(i -> getResult(i,
                                                          aBoolean,
                                                          repositoryPath,
                                                          loremIpsumContentArray ))
                                 .collect(Collectors.toList());

getResult функция:

try
{
    Repository repository = repositoryPath.getRepository();
    String path = RepositoryFiles.relativizePath(repositoryPath);

    //return aBoolean.compareAndSet(aBoolean.get(), !aBoolean.get()) ?
    return aBoolean.getAndSet(!aBoolean.get()) ?
                              new Store(new ByteArrayInputStream(loremIpsumContentArray[i / 2]), repository, path, lock).call() :
                              new Fetch(repository, path, lock).call();
}

Как видно из приведенного вышекод использует параллельные потоки, а затем вызывает getResult функцию.Также есть атомная переменная.Когда значение atomicVariable равно true, вызывается функция store, а когда значение false, вызывается функция fetch.

Насколько я понимаю, внутри функции getResult мы проверяем и обновляем атомарную переменную aBoolean, и эта операция проверки и обновления является атомарной, но new Store(...).call(); и new Fetch(...).call(); - нет, и поскольку параллельные потоки включают несколько потоков, поэтому существуетсостояние гонки, возникающее при

return aBoolean.getAndSet(!aBoolean.get()) ?
                          new Store(new ByteArrayInputStream(loremIpsumContentArray[i / 2]), repository, path).call() :
                          new Fetch(repository, path).call();

Чтобы подтвердить мою теорию состояние гонки Я добавил lock, как показано ниже, к new Store(...).call() и new Fetch(...).call(), как показано нижеи тогда все работало нормально:

Lock lock = new ReentrantLock();
AtomicBoolean aBoolean = new AtomicBoolean(true);
List<Long> resultList = IntStream.range(0, 64* 2)
                                 .parallel()
                                 .mapToObj(i -> getResult(i,
                                                          aBoolean,
                                                          repositoryPath,
                                                          loremIpsumContentArray,
                                                          lock))
                                 .collect(Collectors.toList());

И getResult функция:

return aBoolean.getAndSet(!aBoolean.get()) ?
                          new Store(new ByteArrayInputStream(loremIpsumContentArray[i / 2]), repository, path, lock).call() :
                          new Fetch(repository, path, lock).call();

У меня есть следующие вопросы:

  • Это мойправильно ли вы понимаете состояние гонки, как указано выше, и использовал ли я замок так, как он должен быть?

  • Как еще можно избежать состояния гонки?

Пожалуйста, дайте мне знать ваши мысли.

1 Ответ

1 голос
/ 25 апреля 2019

Ваш aBoolean.getAndSet(!aBoolean.get()) не атомарный.Некоторые потоки могут переключаться между !aBoolean.get() и окружающими aBoolean.getAndSet, что может привести к состоянию гонки.

Вам следует синхронизировать блок:

boolean whatToExec;
synchronized (aBoolean) {
    whatToExec = aBoolean.get();
    aBoolean.set(!whatToExec);
}
return whatToExec ? ...

Для чего нужна блокировкаFetch или Store неизвестно.Если там произойдет гонка, у меня нет ответа.

...