信号量僵局

时间:2012-04-15 22:58:13

标签: java multithreading deadlock semaphore

所以我在使用信号量时遇到了问题。 编写一个代码,其中有4个房间和一些访客。每个房间都有一定的上限,可以容纳他们可以容纳的游客数量。因此,进入一个完整的房间将触发wait()。 访客在进入另一个房间之前不得离开房间,因此他们总是在房间里。

public class Semaphore {

  private int placesLeft;

  public Semaphore(int placesInRoom) {
    this.placesLeft = placesInRoom;
  }

  public synchronized void acquire(Visitor visitor) {
    Semaphore sem = visitor.getRoom().getSemaphore();

    try {
      while (placesLeft <= 0) {
        this.wait();
    }

  } catch (InterruptedException e) {}

  sem.release();
  placesLeft--;
}

public synchronized void release() {
  placesLeft++;
  this.notifyAll();
}

当2个人试图进入彼此的房间时出现死锁。 出于某种原因,placesLeft计数也没有正确显示。

那我该怎么办?

编辑:

忙于其他事情,重新回答这个问题。 由于房间满了,问题不会发生,当来自room1的person1想要进入room2并且同时来自room2的person2想要进入room1时,会发生锁定。因为我明白它可能与同步有关吗?它们在发布之前就会卡住,因此不会调用release。据我所知,一个房间的准确和释放不能同时调用。所以基本上room1信号量释放不能被称为cuz同时调用accuire,对于room2也是如此?我是新手编码器,同步还不是那么清楚。 从一个或另一个中删除同步似乎不起作用(也是非常错误)。

6 个答案:

答案 0 :(得分:2)

如何使用构建到Java标准库中的java.util.concurrent.Semaphore而不是实现自己的呢?

java.util.concurrent包有一个很好的tutorial覆盖信号量以及它提供的许多其他有用的同步机制。

答案 1 :(得分:1)

依赖关系图中存在循环时会发生死锁。当2个人试图进入彼此的房间时,这显然是一个循环,死锁是一个自然的后果。

但是,您希望以其他方式处理周期:当周期发生时,人们都会沿着周期移动(可以有超过2人交换房间)。

因此,您应首先确定是否形成了一个周期,然后更改访问者的位置。

答案 2 :(得分:0)

Room添加当前访问者列表,以便您可以在acquire中查看来访者来自此房间的其中一个人正等待进入的房间的情况。您还需要添加访问者等待进入Visitor的房间。

Room comingFrom = visitor.getRoom();
while (placesLeft <= 0) {
    for (Visitor waiter : room.getVisitors()) {
        if (waiter.getWaitingForRoom().equals(comingFrom) {
            // swap the visitors without releasing/acquiring any semaphores and return
        }
    }
    this.wait();
}

我有点不确定检查访客是否正在等待当前访客离开的同一个房间的逻辑。鉴于代码,我无法确定room代表哪个房间。

答案 3 :(得分:0)

要回答您的问题,“我该怎么办?”,如果您发现死锁,请将其中一位死锁的访客移至任何有空位的房间。然后把他带到他真正想要的房间。这基本上允许交换而不违反以下任何规则。

  1. 客房必须永远不得包含超过X个访客
  2. 访客总是在一个房间
  3. 一次只能有一位访客可以更换房间(这确实是问题的关键)
  4. 请记住,那里有大量的信号量锁定策略......

答案 4 :(得分:0)

试试这个:

public class Semaphore {
  private final static Object LOCK = new Object();
  private int placesLeft;

  public Semaphore(int placesInRoom) {
    this.placesLeft = placesInRoom;
  }

  public void acquire(Visitor visitor) {
      synchronized (LOCK) {
          Semaphore sem = visitor.getRoom().getSemaphore();

          try {
              while (placesLeft <= 0) {
                  LOCK.wait();
              }
          } catch (InterruptedException e) {}

        sem.release();
        placesLeft--;
    }
}

public void release() {
    synchronized(LOCK) {
        placesLeft++;
        LOCK.notifyAll();
    }
}

旧代码在各个Semaphore实例上同步。防止死锁非常困难,因为一个实例的acquire()方法调用另一个实例的release()。如果另一个线程当前正在另一个实例上执行release()方法,则对acquire()的调用会阻止。如果第二个线程最终在第一个实例上调用release(),则会出现死锁。

我通过同步对名为LOCK的单个对象替换了各个Semaphore实例上的同步。执行acquire()的线程锁定了LOCK的监视器。因此,在调用release()方法时,不会阻止此线程。因此release()方法将始终终止。这解决了僵局。

答案 5 :(得分:0)

死锁通常由分层信号量系统解决。典型的死锁看起来像

流程A

getSemaphore('A');
getSemaphore('B');

流程B

getSemaphore('B');
getSemaphore('A');

仅适用于在B之前选择A的所有进程。这可以通过编写getSemaphore函数来强制执行带有断言的层次结构来完成。

对于您的具体情况,这并不能很明显地解决问题,但您可以从这个想法中进行推断。

创建迁移队列。当用户想要更换房间时,您的功能可能如下:

ChangeRoom(person, from, to)
{
    getSemaphore('room_queue', 3200);
    enqueue(room_queue, Object(person, from, to));
    releaseSemaphore('room_queue');
}

&#34; 3200&#34;是一个信号量超时。如果在获取信号量后进程被中断,它仍然会使系统死锁。这给了1小时的超时。您可以根据系统稳定性将其设置为1分钟,5秒的逻辑值。 然后有一个队列处理器,它只允许一次传输一个非阻塞信号量

QueueProcessor()
{
    getSemaphore('room_queue', 3200);
    for (transition = dequeue(room_queue))
    {
       if (getNonBlockingSemaphore(transition.to)
       {
          releaseSemaphore(transition.from);
          getSemaphore(transition.to);
       }
       continue;
    }
    releaseSemaphore('room_queue');
    sleep(10);
}

睡眠使队列进程形式压倒处理器。将其设置为适当的检查。您还可以设置中断以仅在房间打开空间或添加转换时拉取队列项。这样一来,如果一个房间已满,它就不会浪费时间试图进入,但每个人都至少会有一次射击才能立即进入。

这会强制转换以获取队列信号量,然后才能获得房间信号量。设置无死锁的层次结构。如果房间已满,用户将永远不会离开队列,但不会使系统死锁。