Почему использование volatile с синхронизированным блоком?
Я видел несколько примеров в java, где они выполняли синхронизацию в блоке кода, чтобы изменить некоторую переменную, в то время как эта переменная была объявлена летучей исходной. Я видел это в примере singleton-класса, где они объявили уникальный экземпляр как изменчивый, и они sychronized блок, который инициализирует этот экземпляр... Мой вопрос заключается в том, почему мы объявляем его изменчивым, пока мы его синхронизируем, почему нам нужно делать оба? не является одним из них, достаточным для другого?
public class someClass {
volatile static uniqueInstance = null;
public static someClass getInstance() {
if(uniqueInstance == null) {
synchronized(someClass.class) {
if(uniqueInstance == null) {
uniqueInstance = new someClass();
}
}
}
return uniqueInstance;
}
заблаговременно.
Ответы
Ответ 1
Синхронизация сама по себе будет достаточной в этом случае, если первая проверка была в синхронизированном блоке (но это не так, и один поток может не увидеть изменения, выполненные другим, если переменная не была изменчивой). Недостаточно одного энергопотребления, потому что вам нужно выполнить более одной операции атомарно. Но будьте осторожны! Здесь у вас есть так называемая двойная проверка - общая идиома, которая, к сожалению, не работает надежно. Я думаю, что это изменилось с Java 1.6, но этот код может быть рискованным.
EDIT: когда переменная изменчива, этот код работает правильно с JDK 5 (не 6, как я писал ранее), но он не будет работать так, как ожидалось, в JDK 1.4 или ранее.
Ответ 2
Это использует двойную проверку блокировки, обратите внимание, что if(uniqueInstance == null)
не находится в пределах синхронизированной части.
Если uniqueInstance
не является изменчивым, он может быть "инициализирован" частично сконструированным объектом, где его части не видны, кроме потока, выполняемого в блоке synchronized
. volatile делает это все или ничего в этом случае.
Если у вас не было синхронизированного блока, вы могли бы получить 2 потока, которые будут получать эту точку одновременно.
if(uniqueInstance == null) {
uniqueInstance = new someClass(); <---- here
И вы создаете 2 объекта SomeClass, которые побеждают цель.
Строго говоря, вам не нужна летучесть, метод мог быть
public static someClass getInstance() {
synchronized(FullDictionary.class) {
if(uniqueInstance == null) {
uniqueInstance = new someClass();
}
return uniqueInstance;
}
}
Но это приводит к синхронизации и сериализации каждого потока, который выполняет getInstance().
Ответ 3
Этот пост объясняет идею неустойчивости.
Он также рассматривается в основной работе, Java Concurrency на практике.
Основная идея заключается в том, что Concurrency включает в себя не только защиту общего состояния, но и видимость этого состояния между потоками: в это место входит volatile. (Этот более крупный контракт определяется Модель памяти Java.)
Ответ 4
Вы можете выполнять синхронизацию без использования синхронизированного блока.
Это не обязательно использовать в нем изменчивую переменную...
volatile обновляет одну переменную из основной памяти.. и
синхронизировано. Обновите все общие переменные, к которым обращались из основной памяти.
Поэтому вы можете использовать его в соответствии с вашими требованиями.
Ответ 5
Мои два цента здесь
Фрист - быстрое объяснение интуиции этого кода
if(uniqueInstance == null) {
synchronized(someClass.class) {
if(uniqueInstance == null) {
uniqueInstance = new someClass();
}
}
}
Причина, по которой он проверяет uniqueInstance == null, состоит в том, чтобы уменьшить накладные расходы при вызове синхронизированного блока, который относительно медленнее. Так называемая блокировка с двойной проверкой.
Во-вторых, причина, по которой он использует синхронизированный, легко понять, он делает две операции внутри атома с синхронизированным блоком.
Последний изменчивый модификатор гарантирует, что все потоки будут видеть одну и ту же копию, поэтому первая проверка за пределами синхронизированного блока увидит значение uniqueInstance таким образом, что это "синхронизировано",
с синхронизированным блоком. Без изменчивого модификатора один поток может назначить значение uniqueInstance, но другой поток может не увидеть его с помощью первой проверки. (Хотя вторая проверка увидит его)