Входящий замок и тупик с Java - PullRequest
8 голосов
/ 24 июня 2011

Может кто-нибудь объяснить мне, как Reentrant lock и deadlock связаны друг с другом на примере кода Java (псевдо)?

Ответы [ 4 ]

13 голосов
/ 24 июня 2011

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

public synchronized void functionOne() {

    // do something

    functionTwo();

    // do something else

    // redundant, but permitted...
    synchronized(this) {
        // do more stuff
    }    
}

public synchronized void functionTwo() {
     // do even more stuff!
}

В одноразовой блокировке у вас возникнет ситуация тупиковой ситуации, когда вы попытаетесь вызвать functionTwo() с functionOne(), потому что поток должен будет ждать блокировки ... которую он удерживает сам.

Конечно, тупик - это злая ситуация, в которой поток 1 удерживает блокировку A и ожидает блокировки B, в то время как поток 2 удерживает блокировку B и ожидает блокировки A. Таким образом, ни одна из них не может продолжаться. Этот пример кода создает тупик:

public synchronized void deadlock() throws InterruptedException {
    Thread th = new Thread() {
        public void run() {
            deadlock();
        }
    }.start();

    th.join();
}

Вызывающий поток пытается дождаться появления порожденного потока, который, в свою очередь, не может вызвать deadlock(), пока не завершится вызов. Ка-бум!

6 голосов
/ 24 июня 2011

Возникает мертвая блокировка, тогда поток ожидает условия, которое никогда не произойдет.

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

ReentrantLock lock1 = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();

public void methodA() {
    lock1.lock();
    lock2.lock();
    // do something and un lock both.
}

public void methodB() {
    lock2.lock();
    lock1.lock();
    // do something and un lock both.
}

Как видите, поток может вызвать метод A и получить lock1, ожидающий lock2, а другой поток вызвать метод B и получить lock2, ожидающий lock1.


Однако поток может заблокировать себя. Примером является ReentrantReadWriteLock, поскольку он не поддерживает обновление блокировки чтения до блокировки записи.

ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
rwl.readLock().lock();
// do we need to update?
rwl.writeLock().lock(); // will wait for the readLock() to be released!

Неясная возможность заблокировать себя - это использование подразумеваемых замков. Статический блок инициализатора неявно ориентирован на многопотоковое исполнение, поэтому используется блокировка, даже если статические блоки инициализатора не synchronized

class A {
     private static int VALUE;
     static {
        Thread t = new Thread() {
            public void run() {
                // waits for the A class to load.
                VALUE = someLongTask();
            }
        };
        t.start();
        // waits for the thread.
        t.join();
    }
}

Опять у вас тупик!

1 голос
/ 24 июня 2011

Вот пример взаимоблокировки с ReentrantLock

class Deadlock {
    private static final ReentrantLock l1 = new ReentrantLock();

    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            public void run() {
                System.out.println("A Trying to lock...");
                l1.lock();
                System.out.println("A Locked...");
                try {
                    Thread t = new Thread(new Runnable() {
                        public void run() {
                            System.out.println("B Trying to lock...");
                            l1.lock();
                            System.out.println("B Must not print");
                            try {
                            } finally {
                                System.out.println("B Trying to unlock...");
                                l1.unlock();
                                System.out.println("B Unlocked...");
                            }
                        }
                    });
                    t.start();
                    try {
                        t.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } finally {
                    System.out.println("A Trying to unlock...");
                    l1.unlock();
                    System.out.println("A Unlocked...");
                }
            }
        });
        t.start();
    }
}

Чтобы разрешить взаимоблокировку, закомментируйте вызов на t.join вместе с приложением try / catch.

0 голосов
/ 24 июня 2011

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

Что касается взаимоблокировки, если вы вызываете защищенный блок кода из защищенного блока кода, вам понадобится повторная блокировка (или вы будете тупиковой во время ожидания на себя).

...