Question

Here's my implementation of the Philosopher dinner concurrence problem:
5 philosophers where each one extends Thread:

the problem is everytime the program finish inside a deadlock. I tried different solutions but no one fixed the problem.
maybe someone can give me an help.

this is my program:

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.");
    }
}
Was it helpful?

Solution

The solution is relatively simple:

  1. Philosopher attempts to get fork on left
    SUCCEED -> Continue to step 2
    FAIL -> wait (for a while)
  2. Philosopher attempts to get fork on right
    SUCCEED -> Continue to step 3
    FAIL -> Release left fork and wait (for a while)
  3. Eat and release both forks. Then wait (for a while)

The point to emphasize here is that anytime a philosopher fails to get both forks, he needs to drop any forks he is holding and wait a bit or deadlock will eventually occur.

Perhaps more importantly, what kind of moron uses two forks to eat?

-- EDIT --

Here is a quick example for the 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();
    }
}

Your idea of using a Semaphore object would work just as well. Good luck!

OTHER TIPS

I don't have an answer, but I have a couple of suggestions:

(1) This is minor, but don't override Thread: Override Runnable. It's a good habit, but I don't have time to explain why. Just do it.

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

(2) Don't use synchronized for this kind of problem: Using java.util.concurrent.locks.Lock will give you a lot more flexibility in how you structure your code.

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) As @Holger has pointed out, you have chosen a strategy that is prone to deadlock; You'll have to try something else. Unfortunately your strategy is deeply embedded in your code. That means a lot of re-writing for each new strategy that you try.

Think about how you might isolate the strategy from the rest of the code. What if you defined a 'Strategy' interface, and what if each of your Philosopher instances used a Strategy to grab the chopsticks.

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();
        }
    }
}

Now, each time you want to try something different, you only have to change the grabSticks() method...

...or methods! There's no reason why each Philosopher has to use the same strategy.

Is my Strategy interface good enough? It's a start anyway, but maybe you could improve on it. Could it be extended to provide the Philosophers with some more sophisticated way of cooperating than just mutely grabbing the chopsticks?

Good luck, and Have fun!

Your strategy is to lock on the left fork (or chopstick? Seems you can’t decide…) first. But every philosopher has a different notion of “left fork”. It should be easy to image that it is possible to get into the situation where every philosopher has locked his/her “left fork” in the first step and thus no one can proceed as the “right fork” is locked by the one who sees it as his/her “left fork”.

But you should recognize that situation from your debug output…


As a hint how I would solve such a problem: It is possible to represent the entire table state within a single int value, assigning each fork one bit. So it would be enough for up to 32 philosophers. Now each philosopher will be initialized with a bitmask telling him/her, which forks (s)he needs. Then a single atomic update can be used to allocate both forks/chopsticks at once. So there is no need to deal with single forks then. The allocation may succeed for both or fail leaving the state unchanged. The only thing the philosopher has to remember, besides the invariably assigned fork bits, is whether (s)he currently owns these two forks. After (s)he has finished eating (s)he will put both forks back in a single update as well.

This strategy would solve the deadlock problem but not the theoretically possible starvation of one philosopher (though it is rather unlikely). For my real life applications that is not an issue, I never create multi-threaded code that relies on lock fairness.

But if you want to preclude the possibility of starvation entirely you can expand the algorithm described above to a subclass of AbstractQueuedSynchronizer. This class is an extension to the concept of using an atomic int to represent the state and supports waiting for the desired resources (bits) to become available. It’s class documentation describes how fair waiting can be implemented so no philosopher has to starve…

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top