Безопасность определяется переходным отношением случай-до следующим образом:
Два действия могут быть заказаны с помощью отношения "до".Если одно действие происходит раньше другого, то первое видно и упорядочено перед вторым.
Если у нас есть два действия x и y , мы пишем hb (x, y) , чтобы указать, что x происходит до y .
- Если x и y являются действиями одного потока, а x предшествует yв программном порядке, тогда hb (x, y) .
- Существует ребро произойдет до от конца конструктора объекта до началафинализатор (§12.6) для этого объекта.
- Если действие x синхронизируется с следующим действием y , то мы также имеем hb (x, y) .
- Если hb (x, y) и hb (y, z) , то hb (x, z) .
В предыдущем разделе указано
Действие по разблокировке монитора m синхронизирует с все последующие действия блокировки на m (где «последующие» определены в соответствии с синхронизациейпорядок ронизации).
, который позволяет заключить то, что в спецификации также явно указано:
Из приведенных выше определений следует:
- Разблокировка на мониторе происходит до при каждой последующей блокировке на этом мониторе.
…
Мы можем применить эти правила к вашей программе:
- Первый поток, назначающий
null
для box.supplier
, делает это перед освобождением блока монитора (оставляя synchronized (box) { … }
).Это упорядочено в самом потоке из-за первого маркера («Если x и y являются действиями одного и того же потока и x предшествует y в программном порядке, то hb (x, y) ») - Второй поток, впоследствии получающий тот же монитор (входящий в блок
synchronized (box) { … }
), имеет отношение случай-до к выпуску монитора первым потоком (как указано выше, «Разблокировка намонитор происходит до каждая последующая блокировка на этом мониторе ”) - Считывание второго потока переменной
box.supplier
в блоке synchronized
снова упорядочивается с получением монитораиз-за программного порядка («Если x и y являются действиями одного потока и x предшествует y в программном порядке, то hb (x, y) ») - Теперь триОтношения, указанные выше, могут быть объединены благодаря последнему правилу: «Если hb (x, y) и hb (y, z) , то hb (x, z)».Эта транзитивность позволяет нам сделать вывод, что существует потокобезопасное упорядочение между записью
null
в box.supplier
и последующим чтением переменной box.supplier
, обе в пределах synchronized
блока на одном и том же объекте.
Обратите внимание, что это не имеет ничего общего с тем фактом, что box.supplier
является переменной-членом объекта, который мы используем для synchronized
.Важным аспектом является то, что оба потока используют один и тот же объект в synchronized
, чтобы установить порядок, который взаимодействует с другими действиями из-за правила транзитивности.
Но полезно синхронизировать объект, член которогомы хотим получить доступ, так как это упрощает обеспечение того, что все потоки используют один и тот же объект для синхронизации.Тем не менее, все потоки должны придерживаться одного и того же соглашения, чтобы заставить его работать.
В качестве контрпримера рассмотрим следующий код:
List<SomeType> list = …;
Поток 1:
synchronized(list) {
list.set(1, new SomeType(…));
}
Тема 2:
List<SomeType> myList = list.subList(1, 2);
synchronized(list) {
SomeType value = myList.get(0);
// process value
}
Здесь важнодля потока 2 не использовать myList
для синхронизации, несмотря на то, что мы используем его для доступа к контенту, так как это другой объект.Поток 2 все еще должен использовать исходный экземпляр списка для синхронизации.Это реальная проблема с synchronizedList
, документация которого демонстрирует это на примере доступа к списку через экземпляр Iterator
, который все еще должен быть защищен путем синхронизации с экземпляром List
.