Dining Philosophers problem in Java

Tram Ho

1. Introduction

The Dining Philosophers problem is one of the classical problems used to describe synchronization problems in a multithreaded environment and illustrate the technical ways to solve them. Dijkstra first raised and presented this problem involving computers accessing tape drive peripherals.

The current formula was given by Tony Hoare who is also known for inventing the quicksort sort algorithm. In this article we’ll break down this famous problem and code a common solution.

2. Math problem

The diagram above represents the problem. There are five silent philosophers (P1 – P5) sitting around a round table, spending their lives eating and thinking.

There are five forks for them to share (1 – 5) and to be able to eat, a philosopher needs to hold the fork in both hands. After he finished eating, he put both things down and they could then be picked up by another philosopher who repeated the same cycle.

The goal was to provide a flowchart/process that would help philosophers achieve their goals of eating and thinking without starving to death.

3. Solution

An initial solution would be to make each philosopher follow the following protocol:

As the code above describes, every philosopher is thinking at first. After a certain period of time, the philosopher feels hungry and wants to eat.

At this point, he reaches for the fork on either side, and once he has both, he will continue to eat. After eating, the philosopher would put down his forks, leaving them available to his neighbor.

4. Implementation

We model each of our philosophers as classes that implement the Runnable interface so that we can run them as separate threads. Each Philosopher has access to two forks to his left and right:

We also have a function that instructs a Philosopher to perform an action – eat, think or grab a fork to prepare to eat:

As shown in the above code, each action is simulated by pausing the calling thread for a random amount of time, so the execution instruction is not executed only in time.

Now, let’s implement Philosopher’s core logic.

To simulate grabbing a fork, we need to lock it so that no two Philosopher threads can grab it at the same time.

To achieve this, we use the synchronized keyword to get the internal monitor of the fork object and prevent other threads from doing the same. Now we proceed to implement the run() method in the Philosopher class:

This plan executes exactly the one described earlier: a Philosopher thinks for a moment and then decides to eat.

Then he took the forks to his left and right and began to eat. When he was done, he put down his fork. We also add a timestamp for each action, which will help us understand the order in which events occur.

To start the whole process, we write a client that creates 5 Philosophers as threads and starts them all:

We model each branch as generic Java objects and generate as many branches as the number of philosophers. We pass each Philosopher his left and right branches which he tries to lock with the synchronized keyword.

Running this code results in output similar to the following. Your output will most likely be different from the one given below, mainly because the sleep() method is called in a different amount of time:

All the Philosophers start thinking at first, and we see that Philosopher 1 keeps picking up the left and right forks, then eats and puts them both down, then Philosopher 5 picks it up.

5. Problem with solution: DeadLock

Although it seems that the above solution is correct, there is still a deadlock problem that arises.

Deadlock is a situation in which the process of a system is halted because each process is waiting to get a resource held by some other process.

We can confirm the same by running the above code a few times and checking if the code crashes. Here is a sample output demonstrating the above problem:

In this situation, each Philosopher gets his left fork, but can’t get his right fork because his neighbor already has it. This condition is often referred to as a waiting loop and is one of the conditions that lead to deadlock and system progress.

6. Solve deadlock

As we have seen above, the main reason for deadlock is a circular wait condition where each process waits for a resource being held by some other process. Therefore, to avoid deadlock, we need to ensure that the waiting condition for the circle is broken. There are several ways to achieve this, the simplest is the following:

All the Philosophers reach for their left fork first, except for the first one who reaches for his right fork.

We implement this in our existing code by making a relatively small change in the code:

The change comes in lines 17-19 of the above code, where we introduce a condition that causes the last philosopher to hit his right fork first, instead of the left. This breaks the circular wait condition and we can prevent deadlock.

The following output shows one of the cases where all the Philosophers have a chance to think and eat without causing a deadlock:

It can be verified by running the code multiple times to get the system out of the deadlock that happened before.

7. Conclusion

In this article, we have explored the famous philosopher’s dinner problem and the concepts of waiting and circular deadlock. We’ve coded a simple solution that causes deadlocks and made a simple change to break the wait loop and avoid deadlocks. This is just a start and more complex solutions still exist.

Share the news now

Source : Viblo