Рассмотрим следующий фрагмент кода (который не совсем то, что кажется на первый взгляд).
static class NumberContainer {
int value = 0;
void increment() {
value++;
}
int getValue() {
return value;
}
}
public static void main(String[] args) {
List<NumberContainer> list = new ArrayList<>();
int numElements = 100000;
for (int i = 0; i < numElements; i++) {
list.add(new NumberContainer());
}
int numIterations = 10000;
for (int j = 0; j < numIterations; j++) {
list.parallelStream().forEach(NumberContainer::increment);
}
list.forEach(container -> {
if (container.getValue() != numIterations) {
System.out.println("Problem!!!");
}
});
}
Мой вопрос: чтобы быть абсолютно уверенным, что "Проблема !!!" не будет напечатано, должна ли переменная "значение" в классе NumberContainer быть помечена?
Позвольте мне объяснить, как я это понимаю сейчас.
-
В первом параллельном потоке NumberContainer-123 (скажем) увеличивается на ForkJoinWorker-1 (скажем). Таким образом, у ForkJoinWorker-1 будет обновленный кеш-номер NumberContainer-123.value, который равен 1. (Другие работники fork-join, однако, будут иметь устаревшие кеши NumberContainer-123.value - они будут сохраните значение 0. В какой-то момент эти кэши других рабочих будут обновлены, но это не произойдет сразу.)
-
Первый параллельный поток завершается, но общие потоки рабочих пулов для вилки не убиваются. Затем запускается второй параллельный поток, используя одни и те же общие рабочие потоки пула соединений fork-join.
-
Предположим, что во втором параллельном потоке задача инкремента NumberContainer-123 назначается ForkJoinWorker-2 (скажем). ForkJoinWorker-2 будет иметь собственное кешированное значение NumberContainer-123.value. Если длительное время прошло между первым и вторым приращениями NumberContainer-123, то предположительно ForkJoinWorker-2 кэш-памяти NumberContainer-123.value будет актуальным, то есть значение 1 будет сохранено, и все будет хорошо. Но что, если время, прошедшее между первым и вторым приращениями, если NumberContainer-123 чрезвычайно короткое? Тогда, возможно, кэш ForkJoinWorker-2 для NumberContainer-123.value может быть устаревшим, сохраняя значение 0, вызывая сбой кода!
Является ли мое описание выше правильным? Если да, может кто-нибудь, пожалуйста, скажите мне, какая временная задержка между двумя приращающимися операциями требуется для обеспечения согласованности кеша между потоками? Или если мое понимание ошибочно, то может кто-нибудь, пожалуйста, скажите мне, какой механизм заставляет поточно-локальные кэши "промываться" между первым параллельным потоком и вторым параллельным потоком?