提问者:小点点

避免死锁的同步方法


我有两个同步方法,我正在使用中介设计模式。我试图避免死锁,这是(根据我的理解)例如,当一个线程对变量res1有锁,但需要对变量res2有锁时。另一个线程需要res1的锁,但有res2的锁——导致死锁,对吗?

假设我对死锁的理解是正确的,那么我的问题是我是否解决了这段代码中的死锁问题?

我有两个同步方法和两个线程。

public class Producer extends Thread {
    private Mediator med;
    private int id;
    private static int count = 1;

    public Producer(Mediator m) {
        med = m;
        id = count++;
    }

    public void run() {
        int num;
        while(true) {
            num = (int)(Math.random()*100);
            med.storeMessage(num);
            System.out.println("P-" + id + ": " + num);
        }
    }
}
public class Consumer extends Thread {
    private Mediator med;
    private int id;
    private static int count = 1;

    // laver kopling over til mediator
    public Consumer(Mediator m) {
        med = m;
        id = count++;
    }

    public void run() {
        int num;
        while(true) {
            num = med.retrieveMessage();
            System.out.println("C" + id + ": " + num);
        }
    }
}
public class Mediator {
    private int number;
    private boolean slotFull = false;

    public synchronized void storeMessage(int num) {
        while(slotFull == true) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }  
        slotFull = true;
        number = num;
        notifyAll();
    }

    public synchronized int retrieveMessage() {
        while(slotFull == false) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        slotFull = false;
        notifyAll();
        return number;
    }
}
public class MediatorTest {
    public static void main(String[] args) {
        Mediator mb = new Mediator(); 
        new Producer(mb).start();
        new Producer(mb).start();
        new Producer(mb).start();

        new Consumer(mb).start();
        new Consumer(mb).start();
    }
}

共3个答案

匿名用户

例如,当线程对变量res1有锁但需要对变量res2有锁时

重要的不是有两个变量,重要的是必须有两个(或更多)锁。

名称“res1”和“res2”旨在暗示两个资源,每个资源可能有一个或多个变量,并且每个资源都有自己的锁。这就是你遇到麻烦的地方:

final Object lock1 = new Object();
final Object lock2 = new Object();

public void method1() {
    synchronized (lock1) {
        // Call Thread.sleep(1000) here to simulate the thread losing its time slice.
        synchronized(lock2) {
            doSomethingThatRequiresBothLocks
        }
    }
}

public void method2() {
    synchronized (lock2) {
        // Do the same here 'cause you can't know which thread will get to run first.
        synchronized(lock1) {
            doSomethingElseThatRequiresBothLocks()
        }
    }
}

如果线程A调用方法1(),它很有可能在成功锁定lock1之后但在锁定lock2之前丢失其时间片(即转向运行)。

然后,当线程A等待轮到它再次运行时,线程B调用方法2()。线程B将能够锁定lock2,但随后它会卡住,因为lock1被线程A锁定。此外,当线程A再次运行时,当它试图锁定线程B拥有的lock2时,它将立即被阻塞。两个线程都无法从这一点继续下去。

在真实的代码中,这一点从未如此明显。当它发生在现实生活中时,通常是因为来自两个或多个不同模块的代码之间的一些不可预见的交互,这些模块甚至可能彼此不知道,但它们访问相同的公共资源。

匿名用户

你对基本死锁问题的理解是正确的。关于死锁问题解决方案的有效性的第二个问题,你只有一个锁,所以我默认会说“是”,因为你描述的死锁在这种情况下是不可能的

匿名用户

我同意@ControlAltDel所说的。你对死锁的理解与我的相匹配。虽然死锁有几种不同的表现方式,但你描述的方式——通过涉及的线程(方法)不一致地获取多个监视器会导致死锁。

另一种方法是(例如)在持有锁的情况下Hibernate。正如您正确编码的那样,当生产者发现slotFully=true时,它会等待,放弃锁,因此另一个线程(消费者,它与生产者共享相同的Mediator实例)可以取得进展,这可能会导致该线程在收到通知后也取得进展。如果您选择调用Thread.睡眠()而不是(天真地希望有人会在条件为false时导致睡眠结束),那么它会导致死锁,因为该线程正在睡觉,仍然持有锁,拒绝访问另一个线程。