避免死锁的同步方法

Synchronized methods to avoid deadlock

我有两个同步方法,并且我正在使用调解器设计模式。 我试图避免死锁,这是(根据我的理解)例如,当线程锁定变量 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();
    }
}

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

for example when a thread has a lock on a variable res1 but needs a lock on variable 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 调用 method1(),它很有可能会失去其 时间片(即转向 运行)在成功锁定 lock1 之后,但在锁定 lock2.

之前

然后,当线程 A 再次等待轮到 运行 时,线程 B 调用 method2()。线程 B 将能够锁定 lock2,但由于 lock1 被线程 A 锁定,它会卡住。此外,当线程 A 再次到达 运行 时,它会立即被阻塞它试图锁定线程 B 拥有的 lock2。两个线程都无法从该点继续。


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

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

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

当您使用 synchronized 关键字时,每个对象都有一个锁,可以限制多个线程访问同一代码块或方法。

来到你的问题,它不会死锁。

如果多个线程共享一个class中的两个独立属性,则必须同步对每个变量的访问,但是如果一个线程正在访问其中一个属性而另一个线程正在访问则没有问题另一个同时。

class Cinema {
private long vacanciesCinema1;  private long vacanciesCinema2;
private final Object controlCinema1, controlCinema2;
public Cinema() {
    controlCinema1 = new Object();
    controlCinema2 = new Object();
    vacanciesCinema1 = 20;
    vacanciesCinema2 = 20;
}
public boolean sellTickets1(int number) {
    synchronized (controlCinema1) {
        if (number < vacanciesCinema1) {
            vacanciesCinema1 -= number;
            return true;
        } else {
            return false;
        }
    }
}
public boolean sellTickets2(int number) {
    synchronized (controlCinema2) {
        if (number < vacanciesCinema2) {
            vacanciesCinema2 -= number;
            return true;
        } else {
            return false;
        }
    }
}
public boolean returnTickets1(int number) {
    synchronized (controlCinema1) {
        vacanciesCinema1 += number;
        return true;
    }
}
public boolean returnTickets2(int number) {
    synchronized (controlCinema2) {
        vacanciesCinema2 += number;
        return true;
    }
}
public long getVacanciesCinema1() {
    return vacanciesCinema1;
}

public long getVacanciesCinema2() {
    return vacanciesCinema2;
}

}

class TicketOffice1 implements Runnable {
private final Cinema cinema;

public TicketOffice1(Cinema cinema) {
    this.cinema = cinema;
}
@Override
public void run() {
    cinema.sellTickets1(3);
    cinema.sellTickets1(2);
    cinema.sellTickets2(2);
    cinema.returnTickets1(3);
    cinema.sellTickets1(5);
    cinema.sellTickets2(2);
    cinema.sellTickets2(2);
    cinema.sellTickets2(2);
}

}

public class CinemaMain {
public static void main(String[] args) {
    Cinema cinema = new Cinema();
    TicketOffice1 ticketOffice1 = new TicketOffice1(cinema);
    Thread thread1 = new Thread(ticketOffice1, "TicketOffice1");
    TicketOffice2 ticketOffice2 = new TicketOffice2(cinema);
    Thread thread2 = new Thread(ticketOffice2, "TicketOffice2");
    thread1.start();
    thread2.start();
    try {
        thread1.join();
        thread2.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.printf("Room 1 Vacancies: %d\n", cinema.getVacanciesCinema1());
    System.out.printf("Room 2 Vacancies: %d\n", cinema.getVacanciesCinema2());
}

}