Подтвердить что ты не робот

Почему не логично синхронизировать логику?

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

Никогда не синхронизироваться с логическим

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

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
      }
   }
}
4b9b3361

Ответ 1

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

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

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, созданный с помощью autoboxing (isOn = true), является тем же объектом, что и Boolean.TRUE (или .FALSE), который является одиночным в ClassLoader для всех объектов. Объект блокировки должен быть локальным для класса, в котором он используется, иначе вы будете блокировать один и тот же объект, который другие блоки могут блокировать в других случаях блокировки, если они совершают ту же ошибку.

Правильный шаблон, если вам нужно заблокировать логическое значение, заключается в определении объекта блокировки 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 во время получения.

Ответ 2

private Boolean isOn = false;
public void doSomeStuffAndToggleTheThing(){
   synchronized(isOn){

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

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

enter image description here

Ответ 3

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

Ответ 4

Изменить: Серый ответ правильный.

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