Почему двойная проверка блокировки используется вообще? - PullRequest
5 голосов
/ 23 мая 2011

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

Я изначально не знал, что двойная проверка блокировкисломан , и когда я узнал об этом, это увеличило этот вопрос для меня: почему люди используют его в первую очередь?Разве сравнение и замена лучше?

if (field == null)
    Interlocked.CompareExchange(ref field, newValue, null);
return field;

(Мой вопрос относится и к C #, и к Java, хотя приведенный выше код относится к C #.)

Имеет ли блокировка с двойной проверкойкакое-то неотъемлемое преимущество по сравнению с атомарными операциями?

Ответы [ 5 ]

12 голосов
/ 23 мая 2011

Имеет ли блокировка с двойной проверкой какое-то неотъемлемое преимущество по сравнению с атомарными операциями?

(Этот ответ касается только C #; я понятия не имею, на что похожа модель памяти Java.)

Принципиальным отличием является потенциальная раса. Если у вас есть:

if (f == null)
    CompareExchange(ref f, FetchNewValue(), null)

тогда FetchNewValue () может вызываться произвольно много раз в разных потоках. Один из этих потоков выигрывает гонку. Если FetchNewValue () очень дорогой и вы хотите, чтобы он вызывался только один раз, тогда:

if (f == null)
    lock(whatever)
        if (f == null)
            f = FetchNewValue();

Гарантирует, что FetchNewValue вызывается только один раз.

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

4 голосов
/ 23 мая 2011

В C # он никогда не нарушался, поэтому мы можем пока игнорировать это.

Код, который вы разместили, предполагает, что newValue уже доступно, или не требует (повторного) вычисления.При двойной проверке блокировки вы гарантируете, что только один поток будет фактически выполнять инициализацию.

Однако, как говорится, в современном C # я обычно предпочел бы просто использовать Lazy<T> чтобы справиться с инициализацией.

1 голос
/ 23 мая 2011

Блокировка с двойной проверкой используется, когда происходит значительное снижение производительности при блокировке всего метода. Другими словами, если вы не хотите синхронизироваться с объектом (для которого вызывается метод) или с классом, вы можете использовать двойную проверку блокировки.

Это может быть в том случае, если для блокировки много споров и когда ресурс, защищенный блокировкой, дорогостоящ; каждый хотел бы отложить процесс создания, пока это не требуется. Блокировка с двойной проверкой улучшает производительность, сначала проверяя условие (подсказку о блокировке), чтобы помочь определить, должна ли быть получена блокировка.

Двойная проверка блокировки была нарушена в Java до Java 5, когда была представлена ​​новая модель памяти. До этого вполне возможно, что подсказка блокировки будет истинной в одном потоке и ложной в другом. В любом случае, символ Initialization-on-Demand-Holder является подходящей заменой схемы блокировки с двойной проверкой; Я нахожу это намного проще для понимания.

0 голосов
/ 23 мая 2011

На каком-то уровне «имеет смысл», что значение, которое будет изменяться только при запуске, не должно блокироваться для доступа, но тогда вам нужно добавить некоторую блокировку (которая вам, вероятно, не понадобится) просто в случае, если два потока пытаются получить к нему доступ при запуске, и он работает большую часть времени. Он сломан, но я понимаю, почему его легко поймать.

0 голосов
/ 23 мая 2011

Ну, преимущество only , которое приходит мне в голову, - это (иллюзия) производительности: проверьте не потокобезопасным способом, затем выполните некоторые операции блокировки, чтобы проверить переменную, которая может бытьдорого.Тем не менее, поскольку блокировка с двойной проверкой нарушена таким образом, что исключает какие-либо твердые выводы из проверки, не поддерживающей потоки, и она всегда попахивает мне преждевременной оптимизацией в любом случае, я бы сказал, что нет, никаких преимуществ - это устаревшееИдиома Java-дней - но хотелось бы, чтобы ее исправили.

Редактировать: чтобы быть понятным (э), я считаю, что двойная проверка блокировки - это идиома, которая развивалась как повышение производительности при блокировке и проверке каждый раз, и,грубо говоря, это почти то же самое, что и некапсулированное сравнение и обмен.Я лично также являюсь поклонником инкапсуляции синхронизированных разделов кода, поэтому я считаю, что лучше вызывать другую операцию, чтобы выполнить грязную работу.

...