#java #multithreading
#java #многопоточность
Вопрос:
Вот мой следующий код:
class Test {
private int x = 0;
public void incX() {
synchronized(this) {
x;
}
System.out.println("x is: " x " " Thread.currentThread().getName());
}
public static void main(String[] args) {
Test test = new Test();
Thread t1 = new Thread(() -> {
test.incX();
});
Thread t2 = new Thread(() -> {
test.incX();
});
t1.start();
t2.start();
System.out.println("Done");
}
}
Вот мой вывод:
x is: 2 Thread-1
x is: 1 Thread-0
Здесь поток t2
выводит 2, но тогда поток t1
также должен выводить 2, верно? Поскольку, когда x
равно 2, оно должно быть видно потоку t1
, верно? Итак, почему поток t1
равен 1?
Как это возможно, что поток t2
выводит 2, а затем поток t1
выводит 1? Поскольку поток t2
уже выводит 2, тогда значение x должно быть 2. Итак, как поток t1
все еще может выводить 1? Я неправильно понимаю?
Комментарии:
1. Вы, вероятно, неправильно понимаете.
2. @duffymo: Можете ли вы объяснить мне, что я неправильно понимаю?
3. Другие проделали лучшую работу. Мне нечего добавить, кроме как сказать, что когда код не соответствует вашим ожиданиям, лучше проверить ваши предположения.
Ответ №1:
Начнем с того, что ваш код неправильно синхронизирован. Ваше выражение
"x is: " x " " Thread.currentThread().getName()
считывает значение общей переменной x
без синхронизации, то есть в гонке данных. Однако он, по крайней мере, увидит значение, указанное в предыдущем synchronized
блоке.
Теперь давайте рассмотрим возможные результаты вашей программы. Потоки выполняются шаг за шагом, и шаги всех потоков чередуются. Рассмотрим следующую последовательность:
Thread-0
вводитsynchronized
блок, считываетx == 0
, обновляет его1
и покидает блок. Последующее строковое выражение считывает это значение1
.Thread-1
вводитsynchronized
блок, считываетx == 1
, обновляет его2
и покидает блок. Последующее строковое выражение считывает это значение2
.Thread-1
вводитsynchronized
методprintln
и выводит его результат2
.Thread-0
вводитsynchronized
методprintln
и выводит его результат1.
Ответ №2:
Вся ваша System.out.println
строка очень далека от атомной.
Например, между построением строки и вызовом System.out.println
может произойти много чего.
Давайте рассмотрим эквивалентный блок кода:
public void incX() {
synchronized(this) {
x;
}
String implicit = "x is: " x " " Thread.currentThread().getName();
// <-- "Point X"
System.out.println(implicit);
}
Теперь сценарий может выглядеть следующим образом:
- Поток 1 запускается и продолжается до тех пор, пока не достигнет «точки X», создав строку, которая гласит:
x равно: 1 поток-0
- Поток 2 запускается и продолжается до тех пор, пока не достигнет «точки X», создав строку, которая гласит:
x равно: 2 потока-1
- Поток 2 вызывает
System.out.println
, делая общий результат до сих пор:
x равно: 2 потока-1
- Поток 1 вызывает
System.out.println
, делая общий вывод:
x — это: 2 потока-1 x — это: 1 поток-0
Копирование значения x
в другую переменную внутри synchronized
блока должно быть достаточным, чтобы поведение соответствовало вашим ожиданиям:
public void incX() {
int val;
synchronized(this) {
val = x;
}
System.out.println("x is: " val " " Thread.currentThread().getName());
}