Понимание тупика на простом примере - PullRequest
0 голосов
/ 18 февраля 2019

Я работаю над пониманием основ взаимоблокировки, поэтому придумал код ниже.У меня есть два потока, получающие блокировки в противоположном порядке, но они не блокируются.Когда я запускаю его, я вижу все распечатки.Что я делаю не так?

public class DeadlockBasics {
  private Lock lockA = new ReentrantLock();
  private Lock lockB = new ReentrantLock();

  public static void main(String[] args) {
    DeadlockBasics dk = new DeadlockBasics();
    dk.execute();
  }

  private void execute() {
    new Thread(this::processThis).start();
    new Thread(this::processThat).start();
  }

  // called by thread 1
  public void processThis() {
    lockA.lock();
    // process resource A
    System.out.println("resource A -Thread1");

    lockB.lock();
    // process resource B
    System.out.println("resource B -Thread1");

    lockA.unlock();
    lockB.unlock();
  }

  // called by thread 2
  public void processThat() {
    lockB.lock();
    // process resource B
    System.out.println("resource B -Thread2");

    lockA.lock();
    // process resource A
    System.out.println("resource A -Thread2");

    lockA.unlock();
    lockB.unlock();
  }
}

Ответы [ 4 ]

0 голосов
/ 18 февраля 2019

Две точки:

  1. Отпустите замки в порядке, обратном их получению.То есть processThis должен изменить порядок снятия замков.Для вашего примера порядок не имеет значения.Но если processThis попытается установить новую блокировку на А до того, как снять блокировку на В, снова может произойти тупик.В более общем плане вам будет проще думать о блокировках, рассматривая их объем и избегая перекрывающихся, но не ограничивающих область.
  2. Чтобы лучше выделить проблему, я бы позвонил на wait после полученияпервая блокировка в каждом из потоков и execute запускает оба потока , затем вызывает notify в обоих потоках.
0 голосов
/ 18 февраля 2019

Это может действительно привести к взаимоблокировке, но не всегда, например, если processThis () полностью выполняется, а затем processThat () или наоборот, тупиковая ситуация отсутствует.Вы можете попытаться добавить Thread.delay (100) или Thread.yield (), чтобы направить выполнение потоков в тупик или даже снять разблокировку с определенного тупика.

0 голосов
/ 18 февраля 2019

Ваш код является хорошим примером мертвой блокировки, поскольку ReenttrantLock - это блокировка взаимного исключения с тем же поведением, что и неявная блокировка доступа к монитору с использованием синхронизированного.Однако вы не видите тупик из-за этой части:

private void execute() {
      new Thread(this::processThis).start();
      new Thread(this::processThat).start();
}

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

Я добавил Thread.sleep(); в ваш код, чтобы оба потока могли выполняться как-то параллельно.

package com.company;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class DeadlockBasics {
    private Lock lockA = new ReentrantLock();
    private Lock lockB = new ReentrantLock();

    public static void main(String[] args) {
        DeadlockBasics dk = new DeadlockBasics();
        dk.execute();
    }

    private void execute() {
        new Thread(this::processThis).start();
        new Thread(this::processThat).start();
    }

    // called by thread 1
    private void processThis() {
        lockA.lock();
        // process resource A
        try {
            Thread.sleep(1000); //Wait for thread 2 to be executed
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Thread 1 will own lock a");

        lockB.lock();
        // process resource B
        System.out.println("Thread 1 will own lock b");

        lockA.unlock();
        lockB.unlock();

        // Both locks will now released from thread 1
    }

    // called by thread 2
    private void processThat() {
        lockB.lock();
        // process resource B
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Thread 2 will own lock b");

        lockA.lock();
        // process resource A
        System.out.println("Thread 2 will own lock a");

        lockA.unlock();
        lockB.unlock();

        // Both locks are released by thread 2
    }
}
0 голосов
/ 18 февраля 2019

Прежде всего, нет гарантии, какие потоки запускаются первыми.Чтобы получить взаимоблокировку, один из потоков должен взять блокировку на lockA, а затем второй поток должен взять блокировку на lockB или наоборот.

public void processThis() {
    lockA.lock();
    // here the control should be switched to another thread
    System.out.println("resource A -Thread1");

    lockB.lock();
    ...

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

lockA.lock();
Thread.sleep(200);  // 200 milis

Тогда второй поток сможет заблокировать lockB до первого выпуска обоих из них

...