餐饮哲学家代码中出现僵局

时间:2014-05-14 16:19:01

标签: java multithreading deadlock

这是我对哲学家晚餐并发问题的实施:
5个哲学家,每个人都延伸线程:

问题是每次程序在死锁内完成。我尝试了不同的解决方案,但没有人解决问题 也许有人可以给我一个帮助。

这是我的计划:

import java.util.concurrent.ThreadLocalRandom;

class Fork {
    public static final char FORK = '|';
    public static final char NO_FORK = ' ';
    int id;

    public Fork(final int id) {
        this.id = id;
    }
}

class Philosopher extends Thread {
    public static final char PHIL_THINKING = '-';
    public static final char PHIL_LEFT_FORK = '=';
    public static final char PHIL_EATING = 'o';
    private final int id;

    public Philosopher(final int id) {
        this.id = id;
    }

    @Override
    public void run() {
        final int tableOffset = 4 * id;
        final Object leftLock = S5Philosophers.listOfLocks[id];
        final Object rightLock = S5Philosophers.listOfLocks[(id + 1)
                % S5Philosophers.NUM_PHILOSOPHERS];
        final int table__farL = tableOffset + 0;
        final int table__left = tableOffset + 1;
        final int table_philo = tableOffset + 2;
        final int table_right = tableOffset + 3;
        final int table__farR = (tableOffset + 4)
                % (4 * S5Philosophers.NUM_PHILOSOPHERS);

        while (!isInterrupted()) {
            try {
                Thread.sleep(S5Philosophers.UNIT_OF_TIME
                        * (ThreadLocalRandom.current().nextLong(6)));
            } catch (final InterruptedException e) {
                break;
            }
            // Try to get the chopstick on the left
            synchronized (leftLock) {
                synchronized (S5Philosophers.class) {
                    S5Philosophers.dinerTable[table__farL] = Fork.NO_FORK;
                    S5Philosophers.dinerTable[table__left] = Fork.FORK;
                    S5Philosophers.dinerTable[table_philo] = PHIL_LEFT_FORK;
                }

                try {
                    sleep(S5Philosophers.UNIT_OF_TIME * 1);
                } catch (final InterruptedException e) {
                    break;
                }
                // Try to get the chopstick on the right
                synchronized (rightLock) {
                    synchronized (S5Philosophers.class) {
                        S5Philosophers.dinerTable[table_philo] = PHIL_EATING;
                        S5Philosophers.dinerTable[table_right] = Fork.FORK;
                        S5Philosophers.dinerTable[table__farR] = Fork.NO_FORK;
                        //notify();
                    }
                    try {
                        sleep(S5Philosophers.UNIT_OF_TIME * 1);
                    } catch (final InterruptedException e) {
                        break;
                    }
                    // Release fork
                    synchronized (S5Philosophers.class) {
                        S5Philosophers.dinerTable[table__farL] = Fork.FORK;
                        S5Philosophers.dinerTable[table__left] = Fork.NO_FORK;
                        S5Philosophers.dinerTable[table_philo] = PHIL_THINKING;
                        S5Philosophers.dinerTable[table_right] = Fork.NO_FORK;
                        S5Philosophers.dinerTable[table__farR] = Fork.FORK;
                        //notify();
                    }
                }
            }
        }
    }
}

public class S5Philosophers {
    public static final int NUM_PHILOSOPHERS = 5;
    public static final int UNIT_OF_TIME = 50;
    public static final Fork[] listOfLocks = new Fork[NUM_PHILOSOPHERS];
    public static char[] dinerTable = null;

    static {
        for (int i = 0; i < NUM_PHILOSOPHERS; i++)
            listOfLocks[i] = new Fork(i);
    }

    public static void main(final String[] a) {
        final char[] lockedDiner = new char[4 * NUM_PHILOSOPHERS];
        for (int i = 0; i < NUM_PHILOSOPHERS; i++) {
            lockedDiner[4 * i + 0] = Fork.NO_FORK;
            lockedDiner[4 * i + 1] = Fork.FORK;
            lockedDiner[4 * i + 2] = Philosopher.PHIL_LEFT_FORK;
            lockedDiner[4 * i + 3] = Fork.NO_FORK;
        }
        final String lockedString = new String(lockedDiner);

        // safe publication of the initial representation
        synchronized (S5Philosophers.class) {
            dinerTable = new char[4 * NUM_PHILOSOPHERS];
            for (int i = 0; i < NUM_PHILOSOPHERS; i++) {
                dinerTable[4 * i + 0] = Fork.FORK;
                dinerTable[4 * i + 1] = Fork.NO_FORK;
                dinerTable[4 * i + 2] = Philosopher.PHIL_THINKING;
                dinerTable[4 * i + 3] = Fork.NO_FORK;
            }
        }

        for (int i = 0; i < NUM_PHILOSOPHERS; i++) {
            final Thread t = new Philosopher(i);
            // uses this solution to allow terminating the application even if
            // there is a deadlock
            t.setDaemon(true);
            t.start();
        }

        System.out.println("The diner table:");
        long step = 0;
        while (true) {
            step++;

            String curTableString = null;
            synchronized (S5Philosophers.class) {
                curTableString = new String(dinerTable);
            }
            System.out.println(curTableString + "   " + step);

            if (lockedString.equals(curTableString))
                break;
            try {
                Thread.sleep(UNIT_OF_TIME);
            } catch (final InterruptedException e) {
                System.out.println("Interrupted.");
            }
        }
        System.out.println("The diner is locked.");
    }
}

3 个答案:

答案 0 :(得分:2)

解决方案相对简单:

  
      
  1. 哲学家试图在左边岔叉   成功 - &gt;继续第2步   失败 - &gt;等(暂时)
  2.   
  3. 哲学家试图在右边分叉   成功 - &gt;继续第3步   失败 - &gt;释放左叉并等待(暂时)
  4.   
  5. 吃掉并释放两个叉子。然后等待(一会儿)
  6.   

这里强调的一点是,无论什么时候哲学家都没有得到两个分叉,他需要丢弃他所持有的任何分叉并等待或最终会发生死锁。

也许更重要的是,什么样的白痴使用两把叉子吃?

- 编辑 -

以下是Fork的快速示例

class Fork {
    public static final char FORK = '|';
    public static final char NO_FORK = ' ';
    private int id;
    private Lock lock = new ReentrantLock();

    public Fork(final int id) {
        this.id = id;
    }

    public boolean isHeld() {
        return lock.isLocked();
    }

    // returns true if successfully grabbed!
    public synchronized boolean tryToGrab() {
        return lock.tryLock();
    }

    public void letGo() {
        lock.unlock();
    }
}

您使用Semaphore对象的想法也同样有用。祝你好运!

答案 1 :(得分:0)

你的策略是首先锁定分叉(或筷子?似乎你不能决定......)。但每个哲学家都有不同的“左叉”概念。应该很容易想象,有可能进入每个哲学家在第一步锁定他/她的“左叉”的情况,因此没有人可以继续,因为“右叉”被看到的人锁定它是他/她的“左叉”。

但您应该从调试输出中识别出这种情况......


作为提示如何解决这样的问题:可以在单个int值内表示整个表状态,将每个fork分配一位。因此,对于多达32位哲学家来说就足够了。现在每个哲学家都会用一个位掩码来初始化,告诉他/她需要哪些叉子。然后可以使用单个atomic update一次分配叉子/筷子。因此,没有必要处理单一的叉子。分配可能会成功,也可能无法保持状态不变。除了总是分配的叉子之外,哲学家唯一要记住的是他现在是否拥有这两个叉子。在他吃完之后,他会将两把叉子放回一次更新中。

这种策略可以解决僵局问题,但不能解决一位哲学家在理论上可能存在的饥饿问题(虽然这种情况不太可能)。对于我的现实生活应用程序而言,我从不创建依赖于锁公平性的多线程代码。

但是如果你想完全排除饥饿的可能性,你可以将上述算法扩展到AbstractQueuedSynchronizer的子类。此类是使用原子int表示状态并支持等待以使所需资源(位)变为可用的概念的扩展。它的课程文档描述了如何实现公平等待,所以没有哲学家必须挨饿......

答案 2 :(得分:0)

我没有答案,但我有几点建议:

(1)这是次要的,但不要覆盖Thread:Override Runnable。这是一个好习惯,但我没有时间解释原因。就这么做。

class Philosopher implements Runnable { ... }
...
Thread t = new Thread(new Philosopher());

(2)不要使用synchronized来解决这类问题:使用java.util.concurrent.locks.Lock可以更灵活地构建代码。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

Lock[] listOfLocks = new Lock[NUM_PHILOSOPHERS];
for (int i=0 ; i<listOfLocks.length ; i++) {
    listOfLocks[i] = new ReentrantLock();
}

(3)正如@Holger指出的那样,你选择了一种容易出现死锁的策略;你必须尝试别的东西。不幸的是,您的策略深深嵌入您的代码中。这意味着您尝试的每个新策略都会进行大量的重写。

考虑如何将策略与其余代码隔离开来。如果您定义了“策略”,该怎么办?如果您的每个哲学家实例都使用策略来抓住筷子,那该怎么办呢?

interface Strategy {
    //returns after locking both the leftLock and the rightLock
    public void grabSticks(Lock leftLock, Lock rightLock);
}

class Philosopher implements Runnable {
    private final Strategy strategy;

    public Philosopher(Strategy strategy) {
        this.strategy = strategy;
    }

    @Override
    public void run() {
        ...
        while (...) {
            think();
            strategy.grabSticks(leftLock, rightLock);
            eat();
            leftLock.unlock();
            rightLock.unlock();
        }
    }
}

现在,每次你想尝试不同的东西时,你只需要改变grabSticks()方法......

......或方法!每个哲学家都没有必要使用相同的策略。

我的策略界面是否足够好?无论如何,这是一个开始,但也许你可以改进它。是否可以扩展为哲学家提供一些更复杂的合作方式,而不仅仅是悄悄地抓住筷子?

祝你好运,玩得开心!