Статьи

Что такое реентерабельные замки?

В Java 5.0 было сделано новое дополнение для расширения возможностей внутренней блокировки, называемое Reentrant Lock. До этого «синхронизированный» и «изменчивый» были средствами для достижения параллелизма.

1
2
3
4
5
public synchronized void doAtomicTransfer(){
     //enter synchronized block , acquire lock over this object.
    operation1()
    operation2();   
} // exiting synchronized block, release lock over this object.

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

Механизм внутренней блокировки является чистым подходом с точки зрения написания кода и довольно хорош для большинства случаев использования. Так зачем нам нужна дополнительная функция явных блокировок? Давайте обсудим.

Механизм внутренней блокировки может иметь некоторые функциональные ограничения, такие как:

  1. Невозможно прервать поток, ожидающий получения блокировки. (прерываемый замок)
  2. Невозможно попытаться получить блокировку, не желая ждать ее вечно. (попробуйте заблокировать)
  3. Невозможно реализовать дисциплины блокировки без блочной структуры, так как внутренние блокировки должны быть освобождены в том же блоке, в котором они были получены.

Кроме того, ReentrantLock поддерживает опрос блокировок и прерывания блокировки, которые поддерживают тайм-аут. ReentrantLock также поддерживает настраиваемую политику справедливости, что позволяет более гибко планировать потоки.

Давайте рассмотрим несколько методов, реализованных в классе ReentrantLock (который реализует Lock):

1
2
3
4
5
void lock();
 void lockInterruptibly() throws InterruptedException;
 boolean tryLock();
 boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
.....

Давайте попробуем разобраться в их использовании и посмотрим, какие преимущества мы можем получить.

  1. Опрос и синхронизация захвата:

    Давайте посмотрим пример кода:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    public void transferMoneyWithSync(Account fromAccount, Account toAccount,
                float amount) throws InsufficientAmountException {
     
            synchronized (fromAccount) {
                // acquired lock on fromAccount Object
                synchronized (toAccount) {
                    // acquired lock on toAccount Object
                    if (amount > fromAccount.getCurrentAmount()) {
                        throw new InsufficientAmountException(
                                "Insufficient Balance");
                    } else {
                        fromAccount.debit(amount);
                        toAccount.credit(amount);
                    }
                }
            }
        }

    В приведенном выше методе TransferMoney () существует вероятность тупиковой ситуации, когда 2 потока A и B пытаются перевести деньги практически одновременно.

    1
    2
    A: transferMoney(acc1, acc2, 20);
    B: transferMoney(acc2, acc1 ,25);

    Возможно, что поток A получил блокировку на объекте acc1 и ожидает получения блокировки на объекте acc2, в то время как поток B получил блокировку на объекте acc2 и ожидает блокировки на acc1. Это приведет к тупику, и система должна быть перезапущена !!

    Однако есть способ избежать этого, который называется «упорядочение блокировки», лично я нахожу это немного сложным.

    Более чистый подход реализован в ReentrantLock с использованием метода tryLock (). Этот подход называется «обнаружение блокировки по времени и по запросу». Это позволяет вам восстановить контроль, если вы не можете получить все необходимые блокировки, отпустите те, которые вы приобрели, и повторите попытку.

    Итак, используя tryLock, мы попытаемся получить обе блокировки, если мы не сможем получить обе, отпустить, если одна из них была получена, затем повторить попытку.

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    public boolean transferMoneyWithTryLock(Account fromAccount,
                Account toAccount, float amount) throws InsufficientAmountException, InterruptedException {
     
            // we are defining a stopTime
            long stopTime = System.nanoTime() + 5000;
            while (true) {
                if (fromAccount.lock.tryLock()) {
                    try {
                        if (toAccount.lock.tryLock()) {
                            try {
                                if (amount > fromAccount.getCurrentAmount()) {
                                    throw new InsufficientAmountException(
                                            "Insufficient Balance");
                                } else {
                                    fromAccount.debit(amount);
                                    toAccount.credit(amount);
                                }
     
                            } finally {
                                toAccount.lock.unlock();
                            }
                        }
     
                    } finally {
                        fromAccount.lock.unlock();
                    }
                }
                if(System.nanoTime() < stopTime)
                    return false;
     
                Thread.sleep(100);
            }//while
        }

    Здесь мы реализовали временную блокировку, поэтому, если блокировки не могут быть получены в течение указанного времени, метод TransferMoney вернет уведомление об ошибке и выйдет изящно.

    Мы также можем поддерживать бюджетные мероприятия, используя эту концепцию.

  2. Приобретение прерываемого замка:

    Обнаружение прерывистой блокировки позволяет использовать блокировку в отменяемых действиях.

    Метод lockInterruptibly позволяет нам попытаться получить блокировку, будучи доступной для прерывания. Так что в основном это означает, что; это позволяет потоку немедленно реагировать на сигнал прерывания, отправленный ему из другого потока.
    Это может быть полезно, когда мы хотим отправить сигнал KILL всем ожидающим блокировкам.

    Давайте посмотрим на один пример. Предположим, у нас есть общая строка для отправки сообщений, и мы хотели бы спроектировать ее так, чтобы, если другой поток приходит и прерывает текущий поток, он снимал блокировку и выполнял операции выхода или завершения работы для отмены текущей задачи. ,

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    public boolean sendOnSharedLine(String message) throws InterruptedException{
            lock.lockInterruptibly();
            try{
                return cancellableSendOnSharedLine(message);
            } finally {
                lock.unlock();
            }
        }
     
    private boolean cancellableSendOnSharedLine(String message){
    .......

    Таймерный tryLock также реагирует на прерывание.

  3. Неблокированная структурированная блокировка:

    В собственных блокировках пары «захват-освобождение» имеют блочную структуру, то есть блокировка всегда освобождается в том же базовом блоке, в котором она была получена, независимо от того, как управление выходит из блока.

    Внешние замки обеспечивают возможность более четкого контроля.
    Некоторые концепции, такие как Lock Strapping, легче реализовать с помощью внешних замков. Некоторые варианты использования встречаются в коллекциях хэша и связанных списках.

  4. Корректность:

    Конструктор ReentrantLock предлагает на выбор два варианта справедливости: создать несправедливую блокировку или справедливую блокировку. При справедливой блокировке потоки могут получать блокировки только в том порядке, в котором они запрашивали, в то время как несправедливая блокировка позволяет блокировке получать ее вне своего хода, это называется блокировкой (разрыв очереди и получение блокировки, когда она стала доступной).

    Добросовестная блокировка имеет значительные затраты производительности из-за накладных расходов на приостановку и возобновление потоков. Могут быть случаи, когда существует значительная задержка между возобновлением приостановленного потока и его фактическим выполнением. Давайте посмотрим на ситуацию:

    1
    2
    3
    A -> holds lock
    B -> has requested and is in suspended state waiting for A to release lock
    C -> requests the lock at the same time when A releases the lock, C has not yet gone to suspended state.

    Поскольку C еще не перешел в приостановленное состояние, есть вероятность, что он может получить блокировку, разблокированную A, использовать ее и отпустить до того, как B даже завершит пробуждение. Таким образом, в этом контексте несправедливая блокировка имеет значительное преимущество в производительности.

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

Ссылка: Что такое реентерабельные замки? от нашего партнера JCG Анирудх Бхатнагар в блоге Анирудх Бхатнагар.