В чем разница между использованием synchronized на lockObject и использованием this как блокировки?
Я знаю разницу между синхронизированным методом и синхронизированным блоком, но не уверен относительно части с синхронизированным блоком.
Предположим, у меня есть следующий код:
class Test {
private int x = 0;
private Object lockObject = new Object();
public void incBlock() {
synchronized(lockObject) {
x++;
}
System.out.println("x=" + x);
}
public void incThis() { // то же самое, что и синхронизированный метод
synchronized(this) {
x++;
}
System.out.println("x=" + x);
}
}
В этом случае в чем разница между использованием lockObject
и использованием this
в качестве блокировки? Они кажутся одинаковыми для меня.
Когда вы решаете использовать синхронизированный блок, как вы определяете, какой объект должен быть использован в качестве блокировки?
4 ответ(ов)
Я почти никогда не использую объект this
в качестве блокировки. Обычно я блокирую приватную переменную, которую я знаю, что другой код не будет блокировать. Если вы блокируете this
, то любой другой код, который знает о вашем объекте, может также попытаться заблокировать его. Хотя это маловероятно, это все же возможно и может привести к взаимным блокировкам или чрезмерным блокировкам.
Нет ничего особенно магического в том, что вы используете для блокировки — вы можете рассматривать это как токен. Любой, кто блокирует с тем же токеном, будет пытаться получить ту же блокировку. Если вы не хотите, чтобы другой код мог получить ту же блокировку, используйте приватную переменную. Я бы также рекомендовал сделать эту переменную final
— я не могу вспомнить ситуацию, в которой я когда-либо хотел бы изменить переменную блокировки на протяжении срока жизни объекта.
У меня тоже был такой вопрос, когда я читал книгу "Java Concurrency In Practice", и я хотел бы добавить немного дополнительной информации к ответам, предоставленным Jon Skeet и spullara.
Вот пример кода, который будет блокировать даже "быстрые" методы setValue(int)
и getValue()
, пока выполняется метод doStuff(ValueHolder)
:
public class ValueHolder {
private int value = 0;
public synchronized void setValue(int v) {
// Или можно использовать блок synchronized(this)...
this.value = v;
}
public synchronized int getValue() {
return this.value;
}
}
public class MaliciousClass {
public void doStuff(ValueHolder holder) {
synchronized(holder) {
// Выполнить что-то "дорогое", так что вызовы сеттера/геттера будут заблокированы
}
}
}
Недостаток использования this
для синхронизации в том, что другие классы могут синхронизироваться по ссылке на ваш класс (разумеется, не через this
). Злонамеренное или непреднамеренное использование ключевого слова synchronized
при блокировке на ссылке вашего объекта может привести к плохому поведению вашего класса при многопоточном использовании, так как внешний класс может эффективно заблокировать ваши методы, синхронизированные на this
, и вы ничего не сможете с этим сделать (в вашем классе) во время выполнения. Чтобы избежать этой потенциальной проблемы, следует синхронизироваться на private final Object
или использовать интерфейс Lock
из пакета java.util.concurrent.locks
.
Для этого простого примера вы также могли бы использовать AtomicInteger
, вместо синхронизации сеттера и геттера.
Каждый объект в Java может выступать в роли монитора. Выбор конкретного объекта зависит от того, какую степень детализации синхронизации вы хотите получить. Использование 'this' имеет свои плюсы и минусы: с одной стороны, это удобный способ синхронизации, с другой — другие классы также могут синхронизироваться на том же мониторе.
Тем не менее, я рекомендую избегать прямого использования ключевого слова synchronized
и вместо этого обращаться к конструкциям из библиотеки java.util.concurrent
, которые предлагают более высокий уровень абстракции и имеют четко определенные семантики. В этой книге содержится много полезных советов от известных экспертов:
Java Concurrency in Practice
http://amzn.com/0321349601
В данном случае не имеет значения, какой объект вы выберете для блокировки. Однако вы должны последовательно использовать один и тот же объект для блокировки, чтобы добиться правильной синхронизации. Приведенный выше код не обеспечивает корректную синхронизацию, так как вы то используете объект 'this' для блокировки, то 'lockObject'.
Что значит 'synchronized'?
Почему нет ConcurrentHashSet, если есть ConcurrentHashMap?
Распределённый контроль параллелизма
Как можно повторно использовать пул потоков после его завершения?
Безопасна ли HttpSession для потоков? Являются ли операции установки/получения атрибутов потокобезопасными?