Java中信号量问题与餐桌哲学家问题

5

我正在尝试学习餐厅哲学家问题中Semaphore的基本要点。现在,我有一个Chopstick类的数组,每个Chopstick都有一个可用许可的信号量:

public class Chopstick
{
    Thread holder = null;
    private Semaphore lock = new Semaphore(1);

    public synchronized void take() throws InterruptedException
    {
        this.lock.acquire();
        holder = Thread.currentThread();

    }

    public synchronized void release()
    {   
        this.lock.release();
        holder = null;
    }
}

holder变量是用于一个函数的,我不确定我是否需要这个函数:

public synchronized void conditionalRelease()
{
    if (holder == Thread.currentThread())
    {
        holder = null;
        this.lock.release();
    }
}

程序可以编译和运行,但是似乎在释放筷子时出现了一些问题。有时候,筷子会被释放,有时候则不会。当所有的筷子都被拿走且一个哲学家感到饥饿时,程序最终会挂起。
以下是哲学家类中释放筷子的代码,该代码会在随机时间后释放筷子:
System.out.println(this.name + " is eating");
Thread.sleep(this.getRandTime());
System.out.println(this.name + " has finished eating");

rightChopstick.release();
System.out.println(this.name + " has released the right chopstick");
leftChopstick.release();
System.out.println(this.name + " has released the left chopstick");

我的程序输出了"哲学家0已经完成用餐",例如,并且继续执行。其他两行从未输出,所以显然我释放的方式有问题。

非常感谢您的帮助。

5个回答

8

我建议你将'method signatures'中的'synchronized'关键字移除。在这种情况下,你正在使用外部锁机制(例如信号量)。而'synchronized'关键字是试图使用对象自己的互斥锁获取锁。现在你正在锁定两个资源,我怀疑这可能会导致死锁。


哈!这正是问题所在……我必须以两种不同的方式实现它,为了一个任务,我复制并粘贴了第一种方法的代码,忘记删除了synchronized关键字。很好的发现。 - Logan Serman

1

确保没有使用任何锁定或同步关键字。下面的筷子代码对我来说运行良好。虽然不是专业人士,但必须给您一些想法。

public class Chopstick {
private boolean inuse;
Semaphore sem;

public Chopstick(){

    inuse = false;
    sem = new Semaphore(1);
}
public void pickUp()
{
    try
    {
        while(inuse)
        {
            try
            {
                sem.acquire();

            }
            catch(InterruptedException e) {}
        }
        inuse = true;
    }catch(Exception e){}
}
public void putDown()
{
    try
    {
        inuse = false;
        sem.release();

    }
    catch (Exception e){}
}

}


1
问题在于当线程1拿到一只特定的筷子时,另一个线程试图获取相同的筷子时,它将在take()方法中的this.lock.acquire();行上等待,但它不会释放对象本身的监视器。
如果现在线程1尝试释放筷子,则无法进入release()方法,因为它仍然被其他等待take()的线程锁定。这是死锁。

1

你目前的做法是在筷子对象上加锁,并将其作为大小为1的信号量。这样做可能会让人有点疑惑,因为信号量通常是用来提供资源的票据的,如果只有一张票,那就等同于互斥锁(无论是同步块还是Lock对象)。建议你考虑将筷子本身作为锁对象。

我之前写了一篇关于Java中餐馆哲学家问题的博客文章,如果有兴趣可以看一下,不过主要是介绍如何通过其他策略避免死锁。


看起来这似乎是作业任务的一部分,他有一些奇怪的要求,声称必须使用信号量。 - Tim Frey
另外一件你可以做的事情是让 Chopstick 扩展 Semaphore(或 Lock)。:) 虽然这不会比直接使用它带来任何好处。 - Alex Miller

0

哲学家在开始进食之前需要同时获取两只筷子的锁,并且会先拿起左边的筷子,然后等待右边的筷子,才开始进食,因此开始方法应该是同步的。 以下方法将使其正常工作:

public synchronized void startEating() {
    leftChopstick.acquire();
    rightChopstick.acquire();
}

public void finishEating(int id) {
    leftChopstick.release();
    rightChopstick.release();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接