Блокировка объектов частными членами класса-лучшая практика? (Ява)


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

В принципе, что является лучшей практикой для блокировки закрытых членов в Java? Предполагая, что каждым частным полем можно управлять только изолированно и никогда вместе (как в примере моего тестового класса ниже), следует ли блокировать каждое частное поле напрямую (Пример 1), или следует использовать общий объект блокировки для каждого частного поля вы хотите заблокировать (Пример 2)?

Пример 1: блокировка закрытых полей напрямую

class Test {
  private final List<Object> xList = new ArrayList<Object>();
  private final List<Object> yList = new ArrayList<Object>();

  /* xList methods */ 

  public void addToX(Object o) {
    synchronized(xList) {
      xList.add(o);
    }
  }

  public void removeFromX(Object o) {
    synchronized(xList) {
      xList.remove(o);
    }
  }

  /* yList methods */ 

  public void addToY(Object o) {
    synchronized(yList) {
      yList.add(o);
    }
  }

  public void removeFromY(Object o) {
    synchronized(yList) {
      yList.remove(o);
    }
  }
}

Пример 2: Используйте объекты блокировки для частного поля

class Test {
  private final Object xLock = new Object();
  private final Object yLock = new Object();
  private List<Object> xList = new ArrayList<Object>();
  private List<Object> yList = new ArrayList<Object>();

  /* xList methods */ 

  public void addToX(Object o) {
    synchronized(xLock) {
      xList.add(o);
    }
  }

  public void removeFromX(Object o) {
    synchronized(xLock) {
      xList.remove(o);
    }
  }

  /* yList methods */ 

  public void addToY(Object o) {
    synchronized(yLock) {
      yList.add(o);
    }
  }

  public void removeFromY(Object o) {
    synchronized(yLock) {
      yList.remove(o);
    }
  }
}
4 3

4 ответа:

Лично я предпочитаю вторую форму. Никакой другой код вообще не может использовать эту ссылку (за исключением отражения, отладки API и т. д.). Вам не нужно беспокоиться о том, пытается ли внутренняя информация списка синхронизироваться на нем. (Любой метод, который вы вызываете в списке, очевидно, имеет доступ к this, поэтому может синхронизироваться с ним.) Вы чисто используете его для блокировки - так что у вас также есть разделение проблем между "Я-замок" и "я-список".

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

Вы можете создать отдельный класс исключительно для использования в качестве мониторов, с переопределением для toString(), которое может помочь в диагностике. Это также сделает цель переменной более ясной.

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

(Лично я хотел бы, чтобы и Java, и .NET не шли по маршруту "каждый объект имеет связанный монитор", но это напыщенная речь для другого дня.)

Пример 1 намного лучше. Поскольку xList являются final, они отлично подходят для синхронизации. Нет необходимости в дополнительных объектах блокировки, излишне усложняющих код и отнимающих память. Только убедитесь,что сам список никогда не подвергается воздействию внешнего мира, нарушающего инкапсуляцию и потокобезопасность.

Однако рассмотрим:

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

Даже если вы уверены, что объект, который вы делаете, никогда не изменится, я считаю более обнадеживающим использовать специальный объект только для блокировки. Это делает его более прозрачным. Если класс будет значительно расширен и / или изменен в будущем кем-то другим, он может найти причину сделать xList не окончательным, не заметив, что он используется для блокировки. Это может быстро привести к проблемам. Потокобезопасность не тривиальна и может стать более сложной, когда код эволюционирует, поэтому сделайте его таким же ясным и как в безопасности, насколько это возможно. Стоимость наличия отдельного объекта только для фиксации невелика по сравнению с затратами на диагностирование проблем с резьбобезопасностью.