Двойная проверка блокировки в JAVA

Читая о DCL в Википедии, я задавался вопросом о проблеме в DCL и предлагаемом решении, или другими словами, почему volatile ключевое слово нужно? Короче говоря, проблема: использование DCL может в некоторых случаях приводить к тому, что частично сконструированный объект используется потоком. Предлагаемое решение (для JAVA 5+):

// Works with acquire/release semantics for volatile
// Broken under Java 1.4 and earlier semantics for volatile
class Foo {
    private volatile Helper helper;
    public Helper getHelper() {
        Helper result = helper;
        if (result == null) {
            synchronized(this) {
                result = helper;
                if (result == null) {
                    helper = result = new Helper();
                }
            }
        }
        return result;
    }

    // other functions and members...
}

Теперь мой вопрос: почему бы не бросить volatile от помощника? поправьте меня, если я ошибаюсь, но если вы просто нарушите эту строку: helper = result = new Helper(); чтобы:

result = new Helper();
helper = result;

В этом сценарии helper никогда не получит ссылку, пока объект не будет завершен. Не правда ли? Как volatile дела лучше?

РЕДАКТИРОВАТЬ: принять этот код:

class Foo {
    private volatile Helper helper;
    public Helper getHelper() {
        Helper result = helper;
        if (result == null) {
            synchronized(this) {
                result = helper;
                if (result == null) {
                    result = new Helper();
                    result.someMethod();
                    helper = result;
                }
            }
        }
        return result;
    }
}

Если следующая строка после инициализации не гарантирует полностью инициализированный объект, я не могу вызвать методы для него. Могу я?

1 ответ

Решение

Если вы опустите volatile, компилятор может оптимизировать result полностью. Эта дополнительная локальная переменная мало что меняет. Это экономит дополнительную нагрузку, но это volatile ключевые слова, которые фиксируют состояние гонки.

Предположим, у нас есть следующий фрагмент кода:

public volatile Object helper;

public synchronized void initHelper() {
    Object result = new Object();
    helper = result;
}

Он будет скомпилирован в следующую псевдо-сборку:

public synchronized void initHelper() {
    Object result = Object.new();
    result.<init>();
    // result is now fully initialized
    // but helper is still null for all threads
    this.helper = result;
    // now all threads can access fully initialized helper
}

Если вы удалите volatile компилятору разрешено предполагать, что никакой другой поток не использует helper и переставить код, чтобы оптимизировать его. Может быть решено удалить ненужную локальную переменную и выдать следующий вывод:

public synchronized void initHelper() {
    this.helper = Object.new();
    // from now on other threads can access helper
    this.helper.<init>();
    // but it's fully initialized only after this line
}

Если вы добавите вызов функции в initHelper, Компилятор никогда не вызовет этот вызов до инициализации. Это изменило бы смысл программы даже в одном потоке. Но разрешено проводить оптимизации, которые нарушат многопоточный код, обращающийся к этому полю.

Другие вопросы по тегам