зачем использовать volatile с синхронизированным блоком? - PullRequest
40 голосов
/ 12 марта 2012

Я видел несколько примеров в java, где они выполняют синхронизацию с блоком кода, чтобы изменить некоторую переменную, в то время как эта переменная была первоначально объявлена ​​как volatile. Я видел это в примере синглтон-класса, где они объявили уникальный экземпляр как volatile, и они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;
    }

спасибо заранее.

Ответы [ 5 ]

18 голосов
/ 12 марта 2012

Синхронизация сама по себе была бы достаточной в этом случае, если бы первая проверка была в синхронизированном блоке (но это не так, и один поток мог бы не видеть изменения, выполненные другим, если переменная не была изменчивой). Одной волатильности будет недостаточно, потому что вам нужно выполнить несколько операций атомарно. Но будьте осторожны! Здесь есть так называемая блокировка с двойной проверкой - распространенная идиома, которая, к сожалению, не работает надежно . Я думаю, что это изменилось с Java 1.6, но все же этот вид кода может быть рискованным.

EDIT : когда переменная является изменчивой, этот код работает правильно с JDK 5 (а не с 6, как я писал ранее), но он не будет работать должным образом в JDK 1.4 или более ранних версиях.

6 голосов
/ 12 марта 2012

При этом используется блокировка с двойной проверкой, обратите внимание, что if(uniqueInstance == null) не находится внутри синхронизированной части.

Если uniqueInstance не является энергозависимым, он может быть "инициализирован" с частично созданным объектом, где деталиэтого не видно никому, кроме потока, выполняющегося в блоке synchronized.volatile делает в этом случае операцию «все или ничего».

Если у вас не было синхронизированного блока, вы можете получить 2 потока, одновременно попадающих в эту точку.

if(uniqueInstance == null) {
      uniqueInstance = new someClass(); <---- here

И вы создаете 2 объекта SomeClass, которые побеждают цель.

Строго говоря, вам не нужен volatile, метод мог бы быть

public static someClass getInstance() {
    synchronized(FullDictionary.class) {
         if(uniqueInstance == null) {
             uniqueInstance = new someClass();
          }
         return uniqueInstance;
    }
}

Но это вызывает синхронизациюи сериализация каждого потока, который выполняет getInstance ().

4 голосов
/ 12 марта 2012

Этот пост объясняет идею, лежащую в основе volatile.

Он также рассматривается в основной работе Параллелизм Java на практике .

Основная идея заключается в том, что параллелизм включает в себя не только защиту общего состояния, но и видимость этого состояния между потоками: именно здесь возникает изменчивость. (Этот более крупный контракт определяется Java Memory Model .)

0 голосов
/ 14 мая 2015

Мои два цента здесь

Фрист быстрое объяснение интуиции этого кода

if(uniqueInstance == null) {
        synchronized(someClass.class) {
            if(uniqueInstance == null) {
                uniqueInstance = new someClass();
            }
        }
    }

Причина, по которой он дважды проверяет uniqueInstance == null, состоит в том, чтобы уменьшить накладные расходы при вызове синхронизированного блока, который относительно медленнее. Так называемая двойная проверка блокировки.

Во-вторых, причина, по которой он использует синхронизированный, проста для понимания, поскольку две операции внутри синхронизированного блока становятся атомарными.

Last модификатор volatile гарантирует, что все потоки видят одну и ту же копию, поэтому самая первая проверка вне синхронизированного блока увидит значение uniqueInstance способом, который «синхронизирован» с синхронизированным блоком. Без модификатора volatile один поток может присвоить значение uniqueInstance, но другой поток может не увидеть его при первой проверке. (Хотя вторая проверка увидит это)

0 голосов
/ 12 марта 2012

Вы можете сделать синхронизацию без использования синхронизированного блока. В нем нет необходимости использовать переменную volatile ... volatile обновляет одну переменную из основной памяти .. и синхронизированный Обновление всех общих переменных, к которым был получен доступ из основной памяти. Так что вы можете использовать его по вашему требованию ..

...