8

所以我在使用信号量时遇到了问题。编写代码,其中有 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计数不正确。

所以我该怎么做?

编辑:

忙于别的事情,重提这个问题。由于房间已满,问题不会发生,当房间 1 的人 1 想进入房间 2 和房间 2 的人 2 想进入房间 1 时发生锁定。据我了解,这可能与同步有关吗?它们在释放之前被卡住,因此不会调用释放。据我了解,一个房间的累积和释放不能同时被调用。所以基本上不能在调用 accuire 的同时调用 room1 信号量释放,对于 room2 也一样?我是新手编码器,同步还不是很清楚。从一个或另一个中删除同步似乎不起作用(也是错误的)。

4

6 回答 6

2

与其自己实现,不如使用Java 标准库中构建的java.util.concurrent.Semaphore怎么样?

java.util.concurrent软件包有一个很棒的教程,涵盖了信号量和它提供的许多其他有用的同步机制。

于 2012-04-15T23:36:18.977 回答
1

当依赖图中存在循环时会发生死锁。当两个人试图进入对方的房间时,这显然是一个循环,僵局是自然的结果。

但是,您想以其他方式处理循环:当循环发生时,人们都沿着循环移动(可以有超过 2 个人交换房间)。

所以你应该先判断是否形成了一个循环,然后再改变访问者的位置。

于 2012-04-16T03:32:08.250 回答
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()then 会阻塞。acquire()如果第二个线程最终调用release()了第一个实例,那么您就有了死锁。

我将单个 Semaphore 实例上的同步替换为一个名为LOCK. 执行的线程acquire()已锁定 的监视器LOCKrelease()因此,该线程在调用该方法时不会被阻塞。该release()方法将因此总是终止。这解决了僵局。

于 2012-12-28T15:28:14.433 回答
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给定代码,我不知道哪个房间代表。

于 2012-04-15T23:13:36.403 回答
0

要回答您的问题“我应该怎么做?”,如果您检测到死锁,请将其中一个死锁的访客移动到任何有空间的房间。然后把他搬到他真正想要的房间。这基本上允许在不违反以下任何规则的情况下进行交换。

  1. 房间内的访客不得超过 X
  2. 访客总是在一个房间里
  3. 一次只有一位访客可以换房间(这确实是问题的症结所在)

请记住,那里有无数的信号量锁定策略......

于 2012-04-15T23:43:57.370 回答
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');
}

“3200”是信号量超时。如果一个进程在发出信号量后被中断,它仍然会使系统死锁。这给出了 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);
}

睡眠使队列进程保持压倒处理器的状态。将其设置为适当的检查。您还可以设置中断以仅在房间打开空间或添加过渡时拉出队列项目。这样,如果房间已满,则不会浪费时间尝试进入,但每个人都至少有一次机会立即进入。

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

于 2015-07-27T18:41:58.277 回答