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