Состояние гонки: минимальный и максимальный диапазон целого числа
Мне недавно задали этот вопрос в интервью.
Учитывая следующий код, каково будет минимальное и максимальное возможное значение статического целого числа num
?
import java.util.ArrayList;
import java.util.List;
public class ThreadTest {
private static int num = 0;
public static void foo() {
for (int i = 0; i < 5; i++) {
num++;
}
}
public static void main(String[] args) throws Exception{
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(new Task());
threads.add(thread);
thread.start();
}
for (int i = 0; i < 5; i++) {
threads.get(i).join();
}
// What will be the range of num ???
System.out.println(ThreadTest.num);
}
}
class Task implements Runnable {
@Override
public void run() {
ThreadTest.foo();
}
}
Я сказал им, что максимальное значение будет 25 (в случае отсутствия условия гонки), а min будет 5 (в случае условия гонки между всеми потоками на каждой итерации).
Но интервьюер сказал, что минимальное значение может быть даже ниже 5.
Как это возможно?
Ответы
Ответ 1
Я утверждаю, что минимально возможное значение составляет 2.
Ключом к этому является неатомичность num++
, т.е. это чтение и запись, которые могут иметь другие операции между ними.
Вызовите темы T1..T5:
- T1 читает 0, T2 читает 0;
- T1 пишет 1, а затем читает и пишет 3 раза.
- Затем T2 пишет 1;
- Тогда T1 читает 1;
- Тогда Т2-5 выполнит всю свою работу
- Затем, наконец, T1 пишет 2.
(Примечание: результат 2 не зависит ни от количества потоков, ни от числа итераций, если их не менее 2).
Но честный ответ на это: это действительно не имеет значения. Существует гонка данных, как определено в JLS 17.4.5:
Когда программа содержит два конфликтующих доступа (§17.4.1 ["Два доступа к одной и той же переменной (чтение или запись в нее) называются конфликтующими, если хотя бы один из них является записью."]), Которые не упорядочены Считается, что с помощью отношения "до того, как это произошло" содержится гонка данных.
(Между действиями в потоках отсутствуют отношения "происходит до того")
Таким образом, вы не можете с пользой полагаться на то, что он делает. Это просто неверный код.
(Более того, я знаю ответ на этот вопрос не из-за какого-то с трудом выигранного многопоточного кода отладки или глубокого технического чтения: я знаю это, потому что я прочитал этот ответ раньше, чем где-либо еще. минимальное значение не очень хороший вопрос для интервью).
Ответ 2
Ваши потоки обновляют переменную, которая не является изменчивой, что означает, что она не гарантирует, что каждый поток увидит обновленное значение num
. Давайте рассмотрим следующий поток выполнения потоков:
Thread 1: 0->1->2 (2 iteration left)
Thread 2: 0->1->2->3 (1 iteration left)
Thread 3: 0->1->2->3 (1 iteration left)
Thread 4: 0->1->2->3 (1 iteration left)
Thread 5: 0->1->2->3 (1 iteration left)
В этот момент поток 1 сбрасывает значение 2
num в память, а поток 2,3,4,5 решает снова прочитать num
из памяти (по любой причине). Сейчас:
Thread 1: 2->3->4 (completed 2 iteration)
Thread 2: 2->3 (completed 1 iteration)
Thread 3: 2->3 (completed 1 iteration)
Thread 4: 2->3 (completed 1 iteration)
Thread 5: 2->3 (completed 1 iteration)
Поток 1 сбрасывает значение 4
в память, и после этого Theard 2,3,4.. сбрасывает значение в память, показывая, что текущее значение числа будет 3
вместо 5
Ответ 3
На мой взгляд, достичь 25 невозможно из-за отсутствия атомарных операций (см. Атомный доступ в руководствах по Java).
Все потоки запускаются практически в одно и то же время, поэтому каждый поток видит значение ThreadTest.num
как 0
в первой итерации. Поскольку существует 5 потоков, параллельно обращающихся к одной и той же переменной, на третьей итерации потоки, вероятно, будут видеть значение ThreadTest.num
еще в виде 1
или 2
и будут неправильно увеличивать значение до 2
или 3
.
В зависимости от оборудования конечное значение будет ниже или выше, самые быстрые могут иметь самые низкие значения, а самые медленные - более высокие. Но, тем не менее, я утверждаю, что максимальное значение не может достигать 25.
ОБНОВЛЕНИЕ (2019-10-07)
Я тестировал себя на своей машине (Core i5 HQ), действительно, конечный результат достиг 25
почти все время. Чтобы лучше понять, я тестировал большее число в цикле for
:
for (int i = 0; i < 10000; i++) {
num++;
}
Теперь, в большинстве случаев, конечный результат был между 20000 и 30000, что далеко от 50000.
Ответ 4
Ну, мой ответ будет Макс 25, и Мин 0, так как все ваши операции являются инкрементными, и вы инициализировали его как 0.. Я думаю, что статический энергонезависимый int брошен туда, чтобы заставить вас задуматься о гонке условия, но есть ли что-нибудь, что уменьшит число в любой ситуации?
Изменение: Для чего бы это ни стоило, это было бы типичным отвлечением, которое они могут ожидать, что вы сможете преодолеть в реальном мире, оправдывая такую "хитрость", есть много красных селедок!