Многопоточная взаимоблокировка — это так просто

Java задняя часть WeChat Операционная система

предисловие

Только лысина может стать сильнее

Оглядываясь назад на фронт:

В этой статье в основном объясняетсятупик, это мой последний пост о многопоточности. В основном будет многопоточнымпроходить через,послеИметь возможность идти дальше!

Взаимная блокировка — важный момент в многопоточности!

Тогда давайте начнем.Если в статье есть ошибки, пожалуйста, потерпите меня и поправьте меня в комментариях~

Отказ от ответственности: в этой статье используется 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.

img

Проект с открытым исходным кодом, охватывающий все точки знаний о бэкэнде Java (уже 6 тысяч звезд):GitHub.com/Zhongf UC очень…

если ты хочешьв реальном времениЕсли вы обратите внимание на мои обновленные статьи и галантерейные товары, которыми я делюсь, поищите в WeChat.Java3y.

Содержимое PDF-документоввсе вручную, если вы ничего не понимаете, вы можете напрямуюспросите меня(В официальном аккаунте есть мои контактные данные).