предисловие
Только лысина может стать сильнее
Оглядываясь назад на фронт:
- ThreadLocal так просто
- Вы можете войти в дверь за три минуты с многопоточностью!
- Базовые знания многопоточности! После прочтения изучение многопоточности может сделать больше с меньшими затратами
- Механизм блокировки Java для понимания
- AQS просто пройти
- Заблокируйте подкласс, чтобы понять
- Вы действительно хотите знать о пуле потоков?
В этой статье в основном объясняетсятупик, это мой последний пост о многопоточности. В основном будет многопоточнымпроходить через,послеИметь возможность идти дальше!
Взаимная блокировка — важный момент в многопоточности!
Тогда давайте начнем.Если в статье есть ошибки, пожалуйста, потерпите меня и поправьте меня в комментариях~
Отказ от ответственности: в этой статье используется JDK1.8.
Во-первых, объяснение блокировки смерти
Используя многопоточность в Java, выМожет вызвать взаимоблокировкупроблема. Тупик сохранит программу навсегдаКартаЖивой, больше не выполнение программы. мы можем только пройтипрервать и перезапуститьспособ повторного выполнения программы.
- Это явление, которое мы очень не желаем видеть, мы хотимнасколько это возможноИзбегайте тупиковых ситуаций!
Взаимная блокировка может быть вызванаобобщатьВ трех предложениях:
- текущий потокесть то, что нужно другим потокамресурс
- текущий потокподождите, пока другой поток не станет владельцемРесурсы
- никогда не сдавайсясобственные ресурсы
1.1 Тупик последовательности блокировки
Сначала давайте посмотрим, как происходит простейший тупик (тупик порядка блокировки):
public class LeftRightDeadlock {
private final Object left = new Object();
private final Object right = new Object();
public void leftRight() {
// 得到left锁
synchronized (left) {
// 得到right锁
synchronized (right) {
doSomething();
}
}
}
public void rightLeft() {
// 得到right锁
synchronized (right) {
// 得到left锁
synchronized (left) {
doSomethingElse();
}
}
}
}
Наша темаСтупенчатое исполнение, то вероятны следующие ситуации:
- Поток А вызывает
leftRight()
способ получить левый замок -
в то же времяПоток B вызывает
rightLeft()
способ получить правильный замок - И поток A, и поток B продолжают выполняться, и поток A в это время нуждается в правильной блокировке.продолжать внизвоплощать в жизнь. На данный момент поток B нуждается в левом замке.продолжать внизвоплощать в жизнь.
- но:Левый замок нити A не освобождается, а правый замок нити B не освобождается..
- Так что они могут только ждать, и это ожидание бесконечно --> ждать вечно --> тупик
1.2 Тупик последовательности динамической блокировки
Давайте посмотрим на следующий пример. Как вы думаете, произойдет ли взаимоблокировка?
// 转账
public static void transferMoney(Account fromAccount,
Account toAccount,
DollarAmount amount)
throws InsufficientFundsException {
// 锁定汇账账户
synchronized (fromAccount) {
// 锁定来账账户
synchronized (toAccount) {
// 判余额是否大于0
if (fromAccount.getBalance().compareTo(amount) < 0) {
throw new InsufficientFundsException();
} else {
// 汇账账户减钱
fromAccount.debit(amount);
// 来账账户增钱
toAccount.credit(amount);
}
}
}
}
код вышеэто выглядит нормально: заблокируйте две учетные записи, чтобы решить, достаточно ли баланса перед передачей!
Однако тот жеможет возникнуть тупик:
- Если две нитив то же времяперечислить
transferMoney()
- Поток A переводит деньги со счета X на счет Y
- Поток B переводит деньги со счета Y на счет X
- Тогда произойдет тупик.
A:transferMoney(myAccount,yourAccount,10);
B:transferMoney(yourAccount,myAccount,20);
1.3 Взаимоблокировка взаимодействующих объектов
Давайте посмотрим на следующий пример:
public class CooperatingDeadlock {
// Warning: deadlock-prone!
class Taxi {
@GuardedBy("this") private Point location, destination;
private final Dispatcher dispatcher;
public Taxi(Dispatcher dispatcher) {
this.dispatcher = dispatcher;
}
public synchronized Point getLocation() {
return location;
}
// setLocation 需要Taxi内置锁
public synchronized void setLocation(Point location) {
this.location = location;
if (location.equals(destination))
// 调用notifyAvailable()需要Dispatcher内置锁
dispatcher.notifyAvailable(this);
}
public synchronized Point getDestination() {
return destination;
}
public synchronized void setDestination(Point destination) {
this.destination = destination;
}
}
class Dispatcher {
@GuardedBy("this") private final Set<Taxi> taxis;
@GuardedBy("this") private final Set<Taxi> availableTaxis;
public Dispatcher() {
taxis = new HashSet<Taxi>();
availableTaxis = new HashSet<Taxi>();
}
public synchronized void notifyAvailable(Taxi taxi) {
availableTaxis.add(taxi);
}
// 调用getImage()需要Dispatcher内置锁
public synchronized Image getImage() {
Image image = new Image();
for (Taxi t : taxis)
// 调用getLocation()需要Taxi内置锁
image.drawMarker(t.getLocation());
return image;
}
}
class Image {
public void drawMarker(Point p) {
}
}
}
вышеgetImage()
иsetLocation(Point location)
Оба должны приобрести два замка
- и работаетЗамок не открывается в пути
ЭтоНеявно получает две блокировки(сотрудничество между объектами)..
СюдаТакже легко вызвать взаимоблокировку.....
Во-вторых, способ избежать взаимоблокировки
Избежание взаимоблокировок можно обобщить тремя способами:
- Фиксированный порядок блокировки(Тупик для порядка блокировки)
- открытый вызов(для взаимоблокировки, вызванной взаимодействием между объектами)
-
Использовать временную блокировку-->
tryLock()
- Если время ожидания получения блокировки истекло, тоСбросить исключение вместо ожидания!
2.1 Фиксированный порядок блокировки, чтобы избежать взаимоблокировки
вышеtransferMoney()
Тупик возникает из-заПорядок блокировкиПоявляется несоответствие~
- Как говорится в книге: если все нитиприобретать замки в фиксированном порядке, то в программе не будет проблемы взаимоблокировки последовательности блокировки!
Тогда в приведенном выше примере мы можемМодернизациянравится:
public class InduceLockOrder {
// 额外的锁、避免两个对象hash值相等的情况(即使很少)
private static final Object tieLock = new Object();
public void transferMoney(final Account fromAcct,
final Account toAcct,
final DollarAmount amount)
throws InsufficientFundsException {
class Helper {
public void transfer() throws InsufficientFundsException {
if (fromAcct.getBalance().compareTo(amount) < 0)
throw new InsufficientFundsException();
else {
fromAcct.debit(amount);
toAcct.credit(amount);
}
}
}
// 得到锁的hash值
int fromHash = System.identityHashCode(fromAcct);
int toHash = System.identityHashCode(toAcct);
// 根据hash值来上锁
if (fromHash < toHash) {
synchronized (fromAcct) {
synchronized (toAcct) {
new Helper().transfer();
}
}
} else if (fromHash > toHash) {// 根据hash值来上锁
synchronized (toAcct) {
synchronized (fromAcct) {
new Helper().transfer();
}
}
} else {// 额外的锁、避免两个对象hash值相等的情况(即使很少)
synchronized (tieLock) {
synchronized (fromAcct) {
synchronized (toAcct) {
new Helper().transfer();
}
}
}
}
}
}
получить соответствующийХэш-значение для фиксации порядка блокировки, так что у нас не будет проблемы взаимоблокировки!
2.2 Открытый вызов, чтобы избежать взаимоблокировки
В случае тупиковой ситуации между взаимодействующими объектами, в основном из-за того, что вПри вызове метода нужно удерживать блокировку, и другие методы с блокировками также вызываются внутри метода!
- Если метод вызывается без удержания блокировки, вызов называется открытым вызовом.!
Мы можем преобразовать его следующим образом:
- Синхронизированные блоки кода лучше всегоИспользуется для защиты только тех операций с участием общего состояния!
class CooperatingNoDeadlock {
@ThreadSafe
class Taxi {
@GuardedBy("this") private Point location, destination;
private final Dispatcher dispatcher;
public Taxi(Dispatcher dispatcher) {
this.dispatcher = dispatcher;
}
public synchronized Point getLocation() {
return location;
}
public synchronized void setLocation(Point location) {
boolean reachedDestination;
// 加Taxi内置锁
synchronized (this) {
this.location = location;
reachedDestination = location.equals(destination);
}
// 执行同步代码块后完毕,释放锁
if (reachedDestination)
// 加Dispatcher内置锁
dispatcher.notifyAvailable(this);
}
public synchronized Point getDestination() {
return destination;
}
public synchronized void setDestination(Point destination) {
this.destination = destination;
}
}
@ThreadSafe
class Dispatcher {
@GuardedBy("this") private final Set<Taxi> taxis;
@GuardedBy("this") private final Set<Taxi> availableTaxis;
public Dispatcher() {
taxis = new HashSet<Taxi>();
availableTaxis = new HashSet<Taxi>();
}
public synchronized void notifyAvailable(Taxi taxi) {
availableTaxis.add(taxi);
}
public Image getImage() {
Set<Taxi> copy;
// Dispatcher内置锁
synchronized (this) {
copy = new HashSet<Taxi>(taxis);
}
// 执行同步代码块后完毕,释放锁
Image image = new Image();
for (Taxi t : copy)
// 加Taix内置锁
image.drawMarker(t.getLocation());
return image;
}
}
class Image {
public void drawMarker(Point p) {
}
}
}
Использование открытого вызоваочень хороший способ, вы должны попробовать использовать его ~
2.3 Используйте временную блокировку
Использовать явную блокировку блокировки, которая используется при получении блокировкиtryLock()
метод. В ожиданииПревышать ограничение по временикогда,tryLock()
Вместо ожидания возвращается сообщение об ошибке.
использоватьtryLock()
Может эффективно избежать проблем с взаимоблокировкой~~
2.4 Обнаружение взаимоблокировки
Хотя причина взаимоблокировки в том, что наш дизайн недостаточно хорош, можно писать код, не зная, где возникла взаимоблокировка.
JDK предоставляет нам два способа обнаружения:
- JconsoleJDK поставляется с инструментом графического интерфейса, JDK для использования наших инструментов JConsole
- Jstack — это инструмент командной строки, который поставляется с JDK и в основном используется для анализа дампа потоков.
Для получения подробной информации см.:
3. Резюме
Взаимоблокировки возникают в основном из-за:
- Чередующееся выполнение между потоками
- решать:зафиксировать в установленном порядке
- Когда метод выполняется, блокировку необходимо удерживать и не снимать
- решать:Уменьшите объем блоков синхронизированного кода, предпочтительнее блокируя только при работе с общими переменными.
- ждать вечно
- Решение: использовать
tryLock()
Временная блокировка, возврат сообщения об ошибке
- Решение: использовать
Глядя на проблему взаимоблокировки на уровне ОС (это очень простое замечание, которое я сделал ранее):
Использованная литература:
- "Основная технология Java, том 1"
- «Практика параллельного программирования на Java»
- «Компьютерная операционная система Тан Сяодань»
Если в статье есть какие-либо ошибки, пожалуйста, поправьте меня, и мы сможем общаться друг с другом. Учащиеся, привыкшие читать технические статьи в WeChat и желающие получить больше ресурсов по Java, могутОбратите внимание на публичный аккаунт WeChat: Java3y.
Проект с открытым исходным кодом, охватывающий все точки знаний о бэкэнде Java (уже 6 тысяч звезд):GitHub.com/Zhongf UC очень…
если ты хочешьв реальном времениЕсли вы обратите внимание на мои обновленные статьи и галантерейные товары, которыми я делюсь, поищите в WeChat.Java3y.
Содержимое PDF-документоввсе вручную, если вы ничего не понимаете, вы можете напрямуюспросите меня(В официальном аккаунте есть мои контактные данные).