Вы должны рассуждать в терминах happens-before
, думая о многопоточном коде в java, это то, что использует JLS
, и это то, что вам следует использовать. Точка.
Возвращаясь к вашему примеру, вы жуете volatile
и synchronized
вместе, как будто они делают одно и то же, вроде как - нет. Даже ваш пример не работает, чтобы «другой» поток видел гарантированную a = 5
, он должен синхронизироваться с такой же блокировкой , чего у вас нет. Тест jcstress доказывает, что вы не правы (я дам вам понять, как именно это запустить)
@JCStressTest
@State
@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "SURPRISE")
@Outcome(id = "0, 1", expect = Expect.ACCEPTABLE, desc = "whatever")
@Outcome(id = "0, 0", expect = Expect.ACCEPTABLE, desc = "whatever")
@Outcome(id = "1, 1", expect = Expect.ACCEPTABLE, desc = "whatever")
public class DifferentSynchronizedObjects {
int x, y;
private Object lock = new Object();
@Actor
public void actor1() {
synchronized (lock) {
x = 1;
y = 1;
}
}
@Actor
public void actor2(II_Result r) {
r.r1 = x;
r.r2 = y;
}
}
Даже если вы не понимаете код, его главная «точка продажи» такова:
@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "SURPRISE")
Вы можете прочитать это как: "пока вы были в синхронизированном состоянии (блокировка) {....}, какой-то другой поток пришел и прочитал x и y". Этот поток чтения видел 1, 0
(x = 1
, y = 0
) и теперь подумайте об этом. Вы были в блоке synchronized
, почему какой-то поток прочитал x = 1
и y = 0
, вы не "защищены"? Нет, ты не. Если вы запустите это - вы получите 1, 0
.
РЕДАКТИРОВАТЬ, чтобы ответить на комментарий
Вы думаете вы защищаете обе записи в x
и y
- но так как JLS
не дает таких гарантий, это ваше понимание вещей, что неправильно. Так просто. Единственная защита, которую вы на самом деле получаете, - это если ваш писатель и читатель будут использовать одну и ту же блокировку.
Оптимизатор может «увидеть», что вы используете это lock
только внутри метода, и, таким образом, преобразовать этот код в (по крайней мере, в теории):
@Actor
public void actor1() {
Object lock = new Object(); // < -- make lock local
synchronized (lock) {
x = 1;
y = 1;
}
}
Поскольку lock
теперь локально для метода, какой смысл вообще его иметь? Никто не может получить к нему доступ, и просто полностью исключить его. Таким образом, вы получаете полностью незащищенный код, который выполняет две независимые записи.
Вывод таков: вы не следуете правилам, которые дает вам JLS
, - будьте готовы к странным результатам.