Блокировка объектов частными членами класса-лучшая практика? (Ява)
Я задал аналогичный вопрос на днях, но не был удовлетворен ответом, главным образом потому, что в коде, который я предоставил, были некоторые проблемы, на которых люди сосредоточились.
В принципе, что является лучшей практикой для блокировки закрытых членов в 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 ответа:
Лично я предпочитаю вторую форму. Никакой другой код вообще не может использовать эту ссылку (за исключением отражения, отладки API и т. д.). Вам не нужно беспокоиться о том, пытается ли внутренняя информация списка синхронизироваться на нем. (Любой метод, который вы вызываете в списке, очевидно, имеет доступ к
this
, поэтому может синхронизироваться с ним.) Вы чисто используете его для блокировки - так что у вас также есть разделение проблем между "Я-замок" и "я-список".Я нахожу, что так проще. чтобы рассуждать о мониторе, как вы можете легко увидеть все возможный код, который его использует.
Вы можете создать отдельный класс исключительно для использования в качестве мониторов, с переопределением для
По общему признанию, этот подход действительно занимает больше памяти, и обычно вам не нужно беспокоиться о блокировке кода наtoString()
, которое может помочь в диагностике. Это также сделает цель переменной более ясной.this
... но я лично чувствую, что польза от разделения беспокойство и отсутствие необходимости беспокоиться о том, что этот код действительно блокирует сам себя, перевешивает затраты на эффективность. Вы всегда можете выбрать первую форму, если обнаружите, что" потерянные " объекты по какой-то причине являются узким местом производительности (и после анализа кода в классе, который вы потенциально собираетесь синхронизировать).(Лично я хотел бы, чтобы и Java, и .NET не шли по маршруту "каждый объект имеет связанный монитор", но это напыщенная речь для другого дня.)
Пример 1 намного лучше. Поскольку
xList
являютсяfinal
, они отлично подходят для синхронизации. Нет необходимости в дополнительных объектах блокировки, излишне усложняющих код и отнимающих память. Только убедитесь,что сам список никогда не подвергается воздействию внешнего мира, нарушающего инкапсуляцию и потокобезопасность.Однако рассмотрим:
Скажем так: второй подход использует больше кода - что дает вам этот дополнительный код? Что касается параллелизма, то они совершенно одинаковы, поэтому это должен быть какой-то другой аспект из общей картины дизайна вашего приложения.
Даже если вы уверены, что объект, который вы делаете, никогда не изменится, я считаю более обнадеживающим использовать специальный объект только для блокировки. Это делает его более прозрачным. Если класс будет значительно расширен и / или изменен в будущем кем-то другим, он может найти причину сделать
xList
не окончательным, не заметив, что он используется для блокировки. Это может быстро привести к проблемам. Потокобезопасность не тривиальна и может стать более сложной, когда код эволюционирует, поэтому сделайте его таким же ясным и как в безопасности, насколько это возможно. Стоимость наличия отдельного объекта только для фиксации невелика по сравнению с затратами на диагностирование проблем с резьбобезопасностью.