Вопрос о блокировке Java
может кто-нибудь объяснить мне, почему в этом коде есть тупик. Спасибо
public class Deadlock {
static class Friend {
private final String name;
public Friend(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public synchronized void bow(Friend bower) {
System.out.format("%s: %s has bowed to me!%n",
this.name, bower.getName());
bower.bowBack(this);
}
public synchronized void bowBack(Friend bower) {
System.out.format("%s: %s has bowed back to me!%n",
this.name, bower.getName());
}
}
public static void main(String[] args) {
final Friend alphonse = new Friend("Alphonse");
final Friend gaston = new Friend("Gaston");
new Thread(new Runnable() {
public void run() { alphonse.bow(gaston); }
}).start();
new Thread(new Runnable() {
public void run() { gaston.bow(alphonse); }
}).start();
}
}
Ответы
Ответ 1
Вот как он, вероятно, будет выполнен.
- Введите
alphonse.bow(gaston);
, теперь привязка заблокирована из-за ключевого слова synchronized
- Введите
gaston.bow(alphonse);
, теперь заблокирован газон
- Не удается выполнить
bower.bowBack(this);
с первого вызова метода bow
, потому что галон (беседка) заблокирован. Подождите, пока блокировка не будет выпущена.
- Не удается выполнить
bower.bowBack(this);
из второго метода вызова bow
, потому что alphonse (bower) заблокирован. Подождите, пока блокировка не будет выпущена.
Оба потока ждут друг друга, чтобы освободить блокировку.
Ответ 2
Рассмотрим следующее:
- Пусть Thread1
run() { alphonse.bow(gaston); }
- Пусть Thread2
run() { gaston.bow(alphonse); }
- Thread1 входит
alphonse.bow(gaston);
, блокировка alphonse
, так как bow()
есть synchronized
- Thread2 входит
gaston.bow(alphonse);
, блокировка gaston
, так как bow()
есть synchronized
- В Thread1,
bower.bowBack(this);
оценивается как gaston.bowBack(alphonse);
- Thread1 пытается получить блокировку для
gaston
, которая в настоящее время поддерживается Thread2
- В Thread2,
bower.bowBack(this);
оценивается как alphonse.bowBack(gaston);
- Thread2 пытается получить блокировку для
alphonse
, которая в настоящее время поддерживается Thread1
- каждый поток ожидает, что другой отпустит блокировку, следовательно, тупик
Проблема в том, что в настоящее время существует чрезмерное synchronized
. Существует много способов "исправить" это; здесь поучительное решение:
public void bow(Friend bower) {
synchronized (this) {
System.out.format("%s: %s has bowed to me!%n",
this.name, bower.getName());
}
bower.bowBack(this);
}
public synchronized void bowBack(Friend bower) {
System.out.format("%s: %s has bowed back to me!%n",
this.name, bower.getName());
}
Теперь bowBack()
полностью synchronized
, но bow()
частично synchronized
частично, используя оператор synchronized(this)
. Это предотвратит тупик.
Вот цитаты из Effective Java 2nd Edition, пункт 67: Избегайте чрезмерной синхронизации
Чтобы избежать сбоев в работе и безопасности, никогда не уступайте управление клиенту в рамках метода или блока synchronized
. Другими словами, внутри области synchronized
не вызывать метод, предназначенный для переопределения или предоставляемый клиентом в виде функционального объекта. С точки зрения class
с областью synchronized
такие методы чужды. Класс не знает, что делает этот метод и не контролирует его. В зависимости от того, что делает инородный метод, вызов его из области synchronized
может вызвать исключения, взаимоблокировки или повреждение данных.
[...] Как правило, вы должны делать как можно меньше работы внутри регионов synchronized
. Получите блокировку, проверьте общие данные, при необходимости измените их и отпустите блокировку.
По существу, bower.bowBack(this)
- это попытка уступить управление чуждому методу, потому что bowBack()
не является final
методом в class Friend
. Рассмотрим следующую попытку исправить проблему, например:
// attempt to fix: STILL BROKEN!!!
public synchronized void bow(Friend bower) {
System.out.format("%s: %s has bowed to me!%n",
this.name, bower.getName());
bower.bowBack(this);
// ceding control to alien method within synchronized block!
}
// not a final method, subclasses may @Override
public void bowBack(Friend bower) {
System.out.format("%s: %s has bowed back to me!%n",
this.name, bower.getName());
}
Вышеприведенный код не будет блокироваться с текущим сценарием alphonse/gaston
, но поскольку bow()
привязывает элемент управления к методу final
bowBack()
, подкласс может @Override
метод таким образом, чтобы выведите bow()
в тупик. То есть bowBack()
является чужой метод bow()
и поэтому НЕ должен быть вызван из области synchronized
.
Ссылки
См. также
- Эффективное Java 2nd Edition
- Пункт 66: Синхронизация доступа к совместно используемым изменяемым данным
- Пункт 15: Минимизировать изменчивость
Ответ 3
Лучший способ понять - поставить ниже код в лук() перед вызовом bower.bowBack
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}