Вопрос по multithreading, java – Почему не рекомендуется синхронизировать логическое значение?

31

Мой архитектор всегда говорит, что

Never synchronize on Boolean

Я не могу понять причину этого и буду очень признателен, если кто-нибудь сможет объяснить на примере, почему это не очень хорошая практика. Пример кода

<code>private Boolean isOn = false;
private String statusMessage = "I'm off";
public void doSomeStuffAndToggleTheThing(){

   // Do some stuff
   synchronized(isOn){
      if(isOn){
         isOn = false;
         statusMessage = "I'm off";
         // Do everything else to turn the thing off
      } else {
         isOn = true;
         statusMessage = "I'm on";
         // Do everything else to turn the thing on
      }
   }
}
</code>
Ваш пример кода взят из сообщения в блоге, объясняющего, почему это плохо?theothertomelliott.com/node/40 James Montagne
@Рэйчел, я, конечно, могу понять ее или его! Есть только два случаяjava.lang.Boolean объекты в работающей JVM, независимо от того, сколько логическихvariables Вы создаете. Это создает серьезный псевдоним: все синхронизируютсяon the same pair objectsв то время как их программа выглядит так, как будто они синхронизируются в совершенно разных случаях! dasblinkenlight
@Rachel Вы синхронизируете объекты, которые вы намеренно делите между потоками, которые нуждаются во взаимном исключении. Вы используете один объект синхронизации на логическую область, которая нуждается в синхронизации. Таких областей может быть много - потенциально - столько, сколько есть переменных, используемых вsynchronized блоки в вашей программе. Допустим, у вас есть десять областей, которые требуют взаимного исключения, поэтому вы создаете десять объектов, по которым вы синхронизируете. Тем не менее, когда ваши объектыBoolean, все ваши десять переменных будут указывать только на два объекта -True а такжеFalseпоэтому ваша программа не будет работать так, как вы предполагали. dasblinkenlight
@dasblinkenlight Также это один из тех редких вопросов, который на самом деле проверяет некоторыеuseful знание собеседника. Я видел несколько трудно найти условия гонки, когда довольно опытные программисты нарушили общий принцип, лежащий в основе этого - хотя Java делает это слишком простым (синхронизация поthis - например, хорошая ошибка, если вы также расширяете поток вместо использования runnable) Voo
@JamesMontagne: Да, но я не совсем понял объяснение. Rachel

Ваш Ответ

4   ответа
1

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

57

never synchronize on Boolean"

Вы должны всегдаsynchronize наconstant object instance, Если вы синхронизируете какой-либо объект, который вы назначаете (то есть меняете объект на новый объект), то он не является постоянным, и разные потоки будут синхронизироваться на другом объекте.instances, Поскольку они синхронизируются на разных экземплярах объекта, несколько потоков будут одновременно входить в защищенный блок, и будут возникать условия гонки. Это тот же ответ для синхронизации наLong, Integer, так далее..

// this is not final so it might reference different objects
Boolean isOn;
...
synchronized (isOn) {
   if (isOn) {
      // this changes the synchronized object isOn to another object
      // so another thread can then enter the synchronized with this thread
      isOn = false;

Что еще хуже (как указал @McDowell) любойBoolean который создается через автобокс (isOn = true) тот же объект, что иBoolean.TRUE (или же.FALSE) который является единственным вClassLoader черезall objects, Ваш объект блокировки должен быть локальным по отношению к классу, в котором он используется, иначе вы будете блокировать тот же единственный объект, который могут блокироваться другими классами в других случаях блокировки, если они делают ту же ошибку.

Правильный шаблон, если вам нужно зафиксировать логическое значение, это определитьprivate final заблокировать объект:

private final Object lock = new Object();
...

synchronized (lock) {
   ...

Или вы должны также рассмотреть возможность использованияAtomicBoolean объект, который означает, что вам, возможно, не придетсяsynchronize на это вообще.

private final AtomicBoolean isOn = new AtomicBoolean(false);
...

// if it is set to false then set it to true, no synchronization needed
if (isOn.compareAndSet(false, true)) {
    statusMessage = "I'm now on";
} else {
    // it was already on
    statusMessage = "I'm already on";
}

В вашем случае, поскольку, похоже, вам нужно включить / выключить его с помощью потоков, вам все равно потребуетсяsynchronize наlock Возьмите объект и установите логическое значение и избегайте условия теста / гонки:

synchronized (lock) {
    if (isOn) {
        isOn = false;
        statusMessage = "I'm off";
        // Do everything else to turn the thing off
    } else {
        isOn = true;
        statusMessage = "I'm on";
        // Do everything else to turn the thing on
    }
}

Наконец, если вы ожидаетеstatusMessage чтобы быть доступным из других потоков, то он должен быть помечен какvolatile если вы не будетеsynchronize во время также.

Хотя это все верно и важно, в ответе отсутствует одна очень важная часть:Never синхронизировать объекты, которыми вы не управляете, и, в особенности, не управлять синглетами, которые являются общими для всей архитектуры. Это сработает ровно один раз, но как только у кого-то возникнет та же идея, у вас возникнут большие проблемы
Похожеprivate final Object lock = new Object(); подход всегда будет рекомендоваться, верно? Rachel
В случае, если вам нужно что-то делать как в выключенном, так и во включенном состоянии, тогда да @Rachel.
@Gray - IIRC, это использованиеAtomicBoolean не заставит синхронизациюstatusMessage ссылка между кешами процессора, если толькоstatusMessage объявлен изменчивым. Но это будет зависеть от условий гонки в любом случае.
Прямо @Rachel. Вот почему никогда не должно быть. Блокировка наBoolean Тип это ошибка.
-3

Что я хочу добавить это: Ваш архитектор прав, если с точки зренияBoolean являетсяimmutableзачем синхронизировать это? Но многопоточность сложна и основана на сценарии.

@ Серый, я знаю, что ты имеешь в виду. В этом сенарио мы должны синхронизировать логический блок. Я исправлю свое английское утверждение.
Вы уверены, что хотите оставить этот ответ здесь?
volatile являетсяnot достаточно потому, что между сетом и геттом есть условие гонки. Тебе необходимоsynchronized блок, чтобы справиться с этим. Также,Boolean значения, которые являются автоматически помещенными, всегда являются одиночными, что означает, что вы должныnever synchronize на них. Смотрите ответ @ McDowell.
16
private Boolean isOn = false;
public void doSomeStuffAndToggleTheThing(){
   synchronized(isOn){

isOn будет ссылаться на тот же объект, что иBoolean.FALSE который общедоступен. Если какой-либо другой фрагмент плохо написанного кода также решит заблокировать этот объект, две совершенно не связанные транзакции должны будут ждать друг друга.

Замки выполняются наэкземпляры объекта, а не на переменные, которые ссылаются на них:

enter image description here

@dasblinkenlight -yuml.me
Можете ли вы привести пример другого плохого кода, который может привести кDeadlock состояние Rachel
Пример? Любой ДРУГОЙ код, который синхронизируется с логическим значением, даже если они называют его чем-то другим, кромеisOn, Есть только два логических объекта, поэтому все эти потоки должны конкурировать друг с другом. Уч.
+1 Мне нравится картина! Вы не нарисовали это, чтобы ответить на этот вопрос?
+1 Это реальная проблема здесь. Хотя я думаю, что несколько более общее объяснение того, почему плохая идея синхронизировать объекты, над которыми у вас нет полного контроля, было бы еще лучше - выполнение этого на одиночном объекте только делает проблему намного более очевидной. Редактировать: комментарий и редактирование поста были сделаны одновременно, теперь я думаю, что это немного яснее.

Похожие вопросы