Common inter thread communication problems

Sleeping Barber Problem

In computer science, the sleeping barber problem is a classic inter-process communication and synchronization problem between multiple operating system processes.

The analogy is based upon a hypothetical barber shop with one barber. The barber has one barber's chair in a cutting room and a waiting room containing a number of chairs in it. When the barber finishes cutting a customer's hair, he dismisses the customer and goes to the waiting room to see if there are others waiting. If there are, he brings one of them back to the chair and cuts their hair. If there are none, he returns to the chair and sleeps in it.

Each customer, when they arrive, looks to see what the barber is doing. If the barber is sleeping, the customer wakes him up and sits in the cutting room chair. If the barber is cutting hair, the customer stays in the waiting room. If there is a free chair in the waiting room, the customer sits in it and waits their turn. If there is no free chair, the customer leaves.

The problem can be solved easily with wait-notify mechanism. We can use a bounded buffer to store the waiting customers. If the queue is empty. the barber thread waits on the buffer. When a new customer is added to the buffer, the barber thread is notified

Here's a code simulating this:

 public class SleepingBarberProblem {

     public static void main(String[] args) {
         Shop shop = new Shop();

         Barber barber = new Barber();
         barber.shop = shop;
         new Thread(barber, "Barber").start();

         for (int i=0; i<10; i++) {
             try {
                 TimeUnit.SECONDS.sleep(2);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             Customer customer = new Customer("Customer-"+i);
             customer.shop = shop;
             new Thread(customer).start();
         }
     }
 }
 class Barber implements Runnable {
     Shop shop;

     @Override
     public void run() {
         while (true) {
             shop.cutHair();
         }
     }
 }
 class Customer implements Runnable {
     Shop shop;
     String name;

     public Customer(String name) { this.name = name; }

     @Override
     public void run() {
         shop.addCustomer(this);
     }

     @Override
     public String toString() {
         return name;
     }
 }
 class Shop {
     int chairs = 3;
     Queue<Customer> queue = new LinkedList<>();

     public void cutHair() {
         synchronized(queue) {
             while (queue.size() == 0) {
                 System.out.println(Thread.currentThread().getName() + " waiting...");
                 try {
                     queue.wait();
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }

             Customer customer = queue.poll();
             workOnCustomer(customer);
         }
     }

     public void workOnCustomer(Customer customer) {
         System.out.println(Thread.currentThread().getName() + " working on Customer # "+customer);
         int timeTaken = 0;
         try {
             Random random = new Random();
             timeTaken = random.nextInt(10);
             TimeUnit.SECONDS.sleep(timeTaken);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println(Thread.currentThread().getName() + " done with on Customer # "+customer + " in time "+timeTaken);
     }

     public void addCustomer(Customer customer) {
         synchronized(queue) {
             if (queue.size() == chairs) {
                 System.out.println(customer + " will leave as wait list is full");                
             }
             else {
                 System.out.println(customer + " will wait");
                 queue.offer(customer);
             }

             if (queue.size() == 1) {
                 System.out.println("Notifying Barber that a customer has arrived");
                 queue.notify();
             }
        }
     }
 }

Explanation:
We have a class Shop which has a certain number of chairs and a queue of customers.
The Shop class has two main operations - adding a customer to queue and processing a customer from queue.
In this scenario, we are assuming that a shop has only one barber - so using mutual exclusion like synchronized block is fine.

To process an customer:

If there are no customers on queue the barber thread goes off scheduling until notified

if there are customers, the customer on top of the queue is picked and processed. To simulate processing, I have used a method taking a random amount of time (between 1-10 seconds):

     public void workOnCustomer(Customer customer) {
         System.out.println(Thread.currentThread().getName() + " working on Customer # "+customer);
         int timeTaken = 0;
         try {
             Random random = new Random();
             timeTaken = random.nextInt(10);
             TimeUnit.SECONDS.sleep(timeTaken);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println(Thread.currentThread().getName() + " done with on Customer # "+customer + " in time "+timeTaken);
     }

Overall the customer processing logic looks like this:

     public void cutHair() {
         synchronized(queue) {
             while (queue.size() == 0) {
                 System.out.println(Thread.currentThread().getName() + " waiting...");
                 try {
                     queue.wait();
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }

             Customer customer = queue.poll();
             workOnCustomer(customer);
         }
     }

The Barber thread should invoke shop.cutHair() as part of it's logic

To add a customer :

If the queue is already full (ie if the queue size == number of chairs), the customer leaves

Else the customer is added to the queue

If there are no other customers in queue, there are chances that the barber is sleeping.. So the barber is notified.

public void addCustomer(Customer customer) {
    synchronized(queue) {
        if (queue.size() == chairs) {
            System.out.println(customer + " will leave as wait list is full");                
        }
        else {
            System.out.println(customer + " will wait");
            queue.offer(customer);
        }            
        if (queue.size() == 1) {
            System.out.println("Notifying Barber that a customer has arrived");
            queue.notify();
        }
    }
}

Now let's define the tasks for the Barber. The barber's actions are really wrapped inside the shop.cutHair() method, so barber task can simply invoke shop.cutHair() in an infinite loop

 class Barber implements Runnable {
     Shop shop;

     @Override
     public void run() {
         while (true) {
             shop.cutHair();
         }
     }
 }

All that the customer task needs to do is to invoke shop.addCustomer() with itself as the parameter

 class Customer implements Runnable {
     Shop shop;
     String name;

     public Customer(String name) { this.name = name; }

     @Override
     public void run() {
         shop.addCustomer(this);
     }

     @Override
     public String toString() {
         return name;
     }
 }

Now the main method - to simulate this, we'll create one thread running Barber task and multiple threads running Customer tasks. For ease of debug/tracing we'll call the customer threads Customer-1, Customer-2 etc. Note that there will be contention between the threads for scheduling as well as locking and there are chances that Customer-5 might be added to the queue after Customer-4 and so on. So if the Barber thread processes Customer-5 before Customer-4, that doesn't mean it's not processing in FIFO order

 public class SleepingBarberProblem {

     public static void main(String[] args) {
         Shop shop = new Shop();

         Barber barber = new Barber();
         barber.shop = shop;
         new Thread(barber, "Barber").start();

         for (int i=0; i<10; i++) {
             try {
                 TimeUnit.SECONDS.sleep(1);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             Customer customer = new Customer("Customer-"+i);
             customer.shop = shop;
             new Thread(customer).start();
         }
     }
 }

Output: (The logs are self explanatory and will give you a pretty good idea about what is happening)

 Barber waiting...
 Customer-0 will wait
 Notifying Barber that a customer has arrived
 Barber working on Customer # Customer-0
 Barber done with on Customer # Customer-0 in time 1
 Barber waiting...
 Customer-1 will wait
 Notifying Barber that a customer has arrived
 Barber working on Customer # Customer-1
 Barber done with on Customer # Customer-1 in time 8
 Barber waiting...
 Customer-4 will wait
 Notifying Barber that a customer has arrived
 Customer-3 will wait
 Customer-2 will wait
 Barber working on Customer # Customer-4
 Barber done with on Customer # Customer-4 in time 2
 Barber working on Customer # Customer-3
 Barber done with on Customer # Customer-3 in time 4
 Barber working on Customer # Customer-2
 Barber done with on Customer # Customer-2 in time 5
 Barber waiting...
 Customer-5 will wait
 Notifying Barber that a customer has arrived
 Barber working on Customer # Customer-5
 Barber done with on Customer # Customer-5 in time 9
 Barber waiting...
 Customer-9 will wait
 Notifying Barber that a customer has arrived
 Customer-8 will wait
 Customer-7 will wait
 Customer-6 will leave as wait list is full
 Barber working on Customer # Customer-9
 Barber done with on Customer # Customer-9 in time 0
 Barber working on Customer # Customer-8
 Barber done with on Customer # Customer-8 in time 2
 Barber working on Customer # Customer-7
 Barber done with on Customer # Customer-7 in time 2
 Barber waiting...

A multiple sleeping barbers problem is variant of this problem that has the additional complexity of coordinating several barbers among the waiting customers.

Dining Philosopher Problem

In computer science, the dining philosophers problem is an example problem often used in concurrent algorithm design to illustrate synchronization issues and techniques for resolving them.

Five silent philosophers sit at a round table with bowls of spaghetti. Forks are placed between each pair of adjacent philosophers like this :
![Dining Philosopher] (
https://pages.mtu.edu/~shene/NSF-3/e-Book/MUTEX/DIAGRAM-philosopher.jpg
)

Each philosopher must alternately think and eat. However, a philosopher can only eat spaghetti when they have both left and right forks. Each fork can be held by only one philosopher and so a philosopher can use the fork only if it is not being used by another philosopher. After an individual philosopher finishes eating, they need to put down both forks so that the forks become available to others. A philosopher can take the fork on their right or the one on their left as they become available, but cannot start eating before getting both forks.

Here are a few potential problems :

  • Lets say every philosopher picks up left fork first, followed by right fork. If every philosopher does that (pick up the left fork)
    simultaneously, there would be no right fork left to pick up causing a deadlock

  • Lets say philosophers eat and think for a variable period of time. Imagine that two philosophers are fast thinkers and fast eaters. They think fast and get hungry fast. Then, they sit down in opposite chairs as shown below. Because they are so fast, it is possible that they can lock their forks and eat. After they finish eating and before their neighbors can lock the forks and eat, they come back again and lock the forks and eat. In this case, the other three philosophers, even though they have been sitting for a long time, they have no chance to eat.

Dining Philosopher Starvation Problem

The primary reason for a deadlock is the circular wait condition where each process waits upon a resource that’s being held by some other process. Hence, to avoid a deadlock situation we need to make sure that the circular wait condition is broken. There are several ways to achieve this, the simplest one is to make all Philosophers reach for their left fork first, except one who first reaches for his right fork.

Here's a piece of code simulating the dining philosopher problem:

 public class DiningPhilosopherProblem {

     static final Philosopher[] philosophers = new Philosopher[5];
     static final Lock[] forks = new Lock[philosophers.length];

     public static void main(String[] args) {
         for (int i=0; i<philosophers.length; i++) {
             Order order = (i == philosophers.length - 1) ? Order.RIGHT_THEN_LEFT : Order.LEFT_THEN_RIGHT;
             Philosopher r = new Philosopher(order);
             philosophers[i] = r;
             forks[i] = new ReentrantLock();
         }

         for (int i=0; i<philosophers.length; i++) {
             philosophers[i].left = forks[left(i)];
             philosophers[i].right = forks[right(i)];
             new Thread(philosophers[i], "Philosopher-"+(i+1)).start();
         }
     }

     static int left(int i) { return i;}
     static int right(int i) { return (i + 1)% philosophers.length; }
 }
 enum Order { LEFT_THEN_RIGHT, RIGHT_THEN_LEFT }
 class Philosopher implements Runnable {

     volatile Lock left;
     volatile Lock right;
     final Order order;

     public Philosopher(Order order) {
         this.order = order;
     }

     @Override
     public void run() {
         while(true) {
             try {
                 doSomething("thinking");

                 if (order == Order.LEFT_THEN_RIGHT) {
                     this.left.lock();
                     this.right.lock();
                 }
                 else {
                     this.right.lock();
                     this.left.lock();
                 }
                 doSomething("eating");
             }
             finally {
                 if (order == Order.LEFT_THEN_RIGHT) {
                     this.right.unlock();
                     this.left.unlock();
                 }
                 else {
                     this.left.unlock();
                     this.right.unlock();
                 }                
             }            
         }        
     }

     public void doSomething(String action) {
         System.out.println(Thread.currentThread().getName() + " currently busy  "+action);
         int timeTaken = 0;
         try {
             Random random = new Random();
             timeTaken = random.nextInt(10);
             TimeUnit.SECONDS.sleep(timeTaken);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println(Thread.currentThread().getName() + " done with "+action);
     }
 }

Explanation:
First let's define a method which sleeps for a random (between 0-10 seconds) amount of time. We'll use this to implement both think and eat functionalities. It's important to have the lower and upper bounds for sleep times close to each other, else it can lead to starvation

     public void doSomething(String action) {
         System.out.println(Thread.currentThread().getName() + " currently busy  "+action);
         int timeTaken = 0;
         try {
             Random random = new Random();
             timeTaken = random.nextInt(10);
             TimeUnit.SECONDS.sleep(timeTaken);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println(Thread.currentThread().getName() + " done with "+action);
     }

We'll use an enum called Order to indicate whether left fork should be picked first or right for should be picked first

 enum Order { LEFT_THEN_RIGHT, RIGHT_THEN_LEFT }

We'll use ReentrantLock instances to represent forks. Picking a fork is akin to invoking lock() on that ReentrantLock object. Each Philosopher will have two locks and Order associated with it.

We'll add two helper methods so that left and right forks are correctly assigned - left fork for ith philosopher is the right fork for (i-1)th philosopher whereas right fork for ith philosopher is the left fork for (i+1)th philosopher. Assuming two arrays of equal size - one for philosophers and other for locks, the helper methods help us associate the correct locks with a philosopher

     static int left(int i) { return i;}
     static int right(int i) { return (i + 1)% philosophers.length; }

Now lets define the Philosopher task - Each philosopher should do the following in an infinite loop
1 Think
2 Acquire forks
3 Eat
4 Release forks (in opposite order of acquiring)

 class Philosopher implements Runnable {

     volatile Lock left;
     volatile Lock right;
     final Order order;

     public Philosopher(Order order) {
         this.order = order;
     }

     @Override
     public void run() {
         while(true) {
             try {
                 doSomething("thinking");

                 if (order == Order.LEFT_THEN_RIGHT) {
                     this.left.lock();
                     this.right.lock();
                 }
                 else {
                     this.right.lock();
                     this.left.lock();
                 }
                 doSomething("eating");
             }
             finally {
                 if (order == Order.LEFT_THEN_RIGHT) {
                     this.right.unlock();
                     this.left.unlock();
                 }
                 else {
                     this.left.unlock();
                     this.right.unlock();
                 }                
             }            
         }        
     }

Finally, lets weave all the pieces together in the main method. To avoid circular wait, we'll make one philosopher pick forks in order opposite to that of every other philosopher

static final Philosopher[] philosophers = new Philosopher[5];
static final Lock[] forks = new Lock[philosophers.length];

public static void main(String[] args) {
    for (int i=0; i<philosophers.length; i++) {
        Order order = (i == philosophers.length - 1) ? Order.RIGHT_THEN_LEFT : Order.LEFT_THEN_RIGHT;
        Philosopher r = new Philosopher(order);
        philosophers[i] = r;
        forks[i] = new ReentrantLock();
    }

    for (int i=0; i<philosophers.length; i++) {
        philosophers[i].left = forks[left(i)];
        philosophers[i].right = forks[right(i)];
        new Thread(philosophers[i], "Philosopher-"+(i+1)).start();
    }
}

Output:

 Philosopher-1 currently busy  thinking
 Philosopher-3 currently busy  thinking
 Philosopher-5 currently busy  thinking
 Philosopher-2 currently busy  thinking
 Philosopher-4 currently busy  thinking
 Philosopher-2 done with thinking
 Philosopher-2 currently busy  eating
 Philosopher-2 done with eating
 Philosopher-2 currently busy  thinking
 Philosopher-5 done with thinking
 Philosopher-5 currently busy  eating
 Philosopher-3 done with thinking
 Philosopher-1 done with thinking
 Philosopher-3 currently busy  eating
 Philosopher-4 done with thinking
 Philosopher-2 done with thinking
 Philosopher-5 done with eating
 Philosopher-5 currently busy  thinking
 Philosopher-5 done with thinking
 Philosopher-3 done with eating
 Philosopher-3 currently busy  thinking
 Philosopher-2 currently busy  eating
 Philosopher-4 currently busy  eating

Cigarette Smoker Problem

The cigarette smokers problem is a common concurrency problem in computer science.

Assume a cigarette requires three ingredients to make and smoke: tobacco, paper, and matches. There are three smokers around a table, each of whom has an infinite supply of one of the three ingredients — one smoker has an infinite supply of tobacco, another has paper, and the third has matches.

There is also a non-smoking agent who enables the smokers to make their cigarettes by arbitrarily (non-deterministically) selecting two of the supplies to place on the table. The smoker who has the third supply should remove the two items from the table, using them (along with their own supply) to make a cigarette, which they smoke for a while. Once the smoker has finished his cigarette, the agent places two new random items on the table. This process continues forever.

The diagram below depicts the interactions between the agent and the smoker :
Cigarette Smoker Problem

This is a problem that be solved quite easily with Semaphores. We can have two Semaphores - one for indicating that consumption is done (waited on by Agent), other for indicating that production is done (waited on by Smokers).

Here's the full code:

public class CigaretteSmokerProblem {

    public static void main(String[] args) {
        new Thread(new Agent(), "Agent").start();
        new Thread(new Smoker(Ingredient.TOBACCO), "SmokerWithTobacco").start();
        new Thread(new Smoker(Ingredient.PAPER), "SmokerWithPaper").start();
        new Thread(new Smoker(Ingredient.MATCH), "SmokerWithMatch").start();
    }
}
enum Ingredient {
    TOBACCO, PAPER, MATCH;    
    public static Ingredient findMissing(Ingredient available1, Ingredient available2) {
        Ingredient missing = null;
        for (Ingredient i : Ingredient.values()) {
            if ((i != available1) && (i != available2)) {
                missing = i;
                break;
            }
        }
        return missing;
    }
}
class Agent implements Runnable {

    static final Ingredient[] available = new Ingredient[2];
    static final Semaphore ingredientConsumedSemaphore = new Semaphore(1);
    static final Semaphore ingredientProducedSemaphore = new Semaphore(0);

    @Override
    public void run() {
        while (true) {

            try {
                ingredientConsumedSemaphore.acquire();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            Ingredient first = getRandomIngredient();
            Ingredient second = getRandomIngredient();
            while (second == first) {
                second = getRandomIngredient();
            }

            available[0]=first;
            available[1]=second;

            System.out.println(Thread.currentThread().getName() + " produced "+first+ " and "+second);

            ingredientProducedSemaphore.release(3);
        }
    }

    private Ingredient getRandomIngredient() {
        Random random = new Random();
        int i = random.nextInt(3);
        return Ingredient.values()[i];
    }
}
class Smoker implements Runnable {
    final Ingredient ingredient;
    static final CyclicBarrier barrier = new CyclicBarrier(3);
    Smoker(Ingredient ingredient){this.ingredient = ingredient;}

    @Override
    public void run() {
        while (true) {
            try {
                Agent.ingredientProducedSemaphore.acquire();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (ingredient == Ingredient.findMissing(Agent.available[0], Agent.available[1])) {
                System.out.println(Thread.currentThread().getName() + " owns "+ingredient+ " while "+Agent.available[0]+ " and "+Agent.available[1]+ " are available now");
                System.out.println(Thread.currentThread().getName() + " is smoking now");
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " is done");

                Agent.ingredientConsumedSemaphore.release();
            }

            try {
                barrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }        
    }
}

Explanation:

First lets define an Enum called ingredient. We'll also create a helper method here which will help us find the missing ingredient given two available ingredients

 enum Ingredient {
     TOBACCO, PAPER, MATCH;    
     public static Ingredient findMissing(Ingredient available1, Ingredient available2) {
         Ingredient missing = null;
         for (Ingredient i : Ingredient.values()) {
             if ((i != available1) && (i != available2)) {
                 missing = i;
                 break;
             }
         }
         return missing;
     }
 }

We'll create two semaphores - one for indicating that items are ready to consume (
ingredientProducedSemaphore ) and other for indicating that items have been consumed (
ingredientConsumedSemaphore). Initially we want to produce - so the corresponding semaphore has permits by default while the other one doesn't

     static final Semaphore ingredientConsumedSemaphore = new Semaphore(1);
     static final Semaphore ingredientProducedSemaphore = new Semaphore(0);

Every smoker, needs to acquire a permit on ingredientProducedSemaphore to proceed. However only the smoker who actually consumed the resource can release the ingredientConsumedSemaphore. That means in every cycle, there will be 3 calls to ingredientProducedSemaphore,acquire() but only one call to
ingredientConsumedSemaphore.release(). So the agent should release 3 permits for
ingredientProducedSemaphore at one go while acquiring only one permit for
ingredientConsumedSemaphore.

class Agent implements Runnable {

    static final Ingredient[] available = new Ingredient[2];
    static final Semaphore ingredientConsumedSemaphore = new Semaphore(1);
    static final Semaphore ingredientProducedSemaphore = new Semaphore(0);

    @Override
    public void run() {
        while (true) {

            try {
                ingredientConsumedSemaphore.acquire();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            Ingredient first = getRandomIngredient();
            Ingredient second = getRandomIngredient();
            while (second == first) {
                second = getRandomIngredient();
            }

            available[0]=first;
            available[1]=second;

            System.out.println(Thread.currentThread().getName() + " produced "+first+ " and "+second);

            ingredientProducedSemaphore.release(3);
        }
    }

    private Ingredient getRandomIngredient() {
        Random random = new Random();
        int i = random.nextInt(3);
        return Ingredient.values()[i];
    }
}

Now lets look into the logic for Smoker class. We have already discussed that every smoker, needs to acquire a permit on ingredientProducedSemaphore to proceed. However only the smoker who actually consumed the resource can release the ingredientConsumedSemaphore. One added problem could that the thread scheduler might run multiple cycles of the same Smoker. For example say Agent produced MATCH and TOBACCO, So ideally the Smoker with Paper should consumer the resource. But there are three Smoker threads running. Lets say scheduler picks up the Smoker with Tobacco first. It consumes a permit from the semaphore to proceed and realized that it cant proceed. However, because its running in an infinite loop and isnt blocked, it might run through the loop twice again before scheduler could process any other Smoker thread. Now all the 3 permits have been consumed and other smoker threads cant proceed. Essentially, we need to take a Smoker off scheduling before all the 3 smokers are done for that round. The easiest way to do it is using a CyclicBarrier of size 3 and call await() on the barrier

class Smoker implements Runnable {
    final Ingredient ingredient;
    static final CyclicBarrier barrier = new CyclicBarrier(3);
    Smoker(Ingredient ingredient){this.ingredient = ingredient;}

    @Override
    public void run() {
        while (true) {
            try {
                Agent.ingredientProducedSemaphore.acquire();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (ingredient == Ingredient.findMissing(Agent.available[0], Agent.available[1])) {
                System.out.println(Thread.currentThread().getName() + " owns "+ingredient+ " while "+Agent.available[0]+ " and "+Agent.available[1]+ " are available now");
                System.out.println(Thread.currentThread().getName() + " is smoking now");
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " is done");

                Agent.ingredientConsumedSemaphore.release();
            }

            try {
                barrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }        
    }
}

Finally the main method to start the 4 threads:

public static void main(String[] args) {
    new Thread(new Agent(), "Agent").start();
    new Thread(new Smoker(Ingredient.TOBACCO), "SmokerWithTobacco").start();
    new Thread(new Smoker(Ingredient.PAPER), "SmokerWithPaper").start();
    new Thread(new Smoker(Ingredient.MATCH), "SmokerWithMatch").start();
}

Output:

 Agent produced MATCH and PAPER
 SmokerWithTobacco owns TOBACCO while MATCH and PAPER are available now
 SmokerWithTobacco is smoking now
 SmokerWithTobacco is done
 Agent produced MATCH and TOBACCO
 SmokerWithPaper owns PAPER while MATCH and TOBACCO are available now
 SmokerWithPaper is smoking now
 SmokerWithPaper is done
 Agent produced PAPER and TOBACCO
 SmokerWithMatch owns MATCH while PAPER and TOBACCO are available now
 SmokerWithMatch is smoking now
 SmokerWithMatch is done
 Agent produced TOBACCO and MATCH
 SmokerWithPaper owns PAPER while TOBACCO and MATCH are available now
 SmokerWithPaper is smoking now
 SmokerWithPaper is done

Let's look at one more design problem very commonly asked in interviews focussing on concurrency :

You have a pool of N threads and an input text with M words (M > N). Design a system such that each thread in the pool prints a word from the input in round robin fashion. No word should get printed out of sequence

This can easily be solved using Semaphores. We have N threads and each thread has a semaphore associated with it. Each thread needs a permit on its semaphore to proceed. Once a thread gets the permit, it picks the next word from queue, prints it and releases permit for the next thread's semaphore so that the next thread can proceed. That way we can easily manage threads running in a round robin fashion without any out of sequence run. The semaphores are all binary semaphores and to begin with, none of them have permits. The process starts by releasing permit for the first semaphore

Here's a code snippet doing the same thing:

 public class SequentialWorkPrinter extends Thread {

     volatile Semaphore current;
     volatile Semaphore next;

     static final Queue<String> input = new LinkedList<>();

     @Override
     public void run() {
         while(true) {
              try {
                  current.acquire();
                  String word = input.poll();
                  if (word == null) {
                      break;
                  }
                  System.out.println(Thread.currentThread().getName() + " : "+word);
              } catch (InterruptedException e) {
                  // ignore
              }
              finally {
                  next.release();
              }            
          }        
     }

     public static void main(String[] args) {
         int poolSize = 4;

         SequentialWorkPrinter[] threads = new SequentialWorkPrinter[poolSize];
         Semaphore[] semaphores = new Semaphore[poolSize];

         for(int i=0; i<poolSize; i++) {
             SequentialWorkPrinter t = new SequentialWorkPrinter();
             t.setName("T"+i);
             threads[i]=t;

             Semaphore semaphore = new Semaphore(0);
             semaphores[i] = semaphore;

             t.current = semaphore;
         }

         for (int i=0; i<poolSize; i++) {
             threads[i].next = semaphores[next(i, poolSize-1)];
             threads[i].start();
         }

         for (String arg : args) {
             SequentialWorkPrinter.input.offer(arg);
         }

         semaphores[0].release();
     }

     private static int next(int i, int max) {
         if (i<max) {
            return i+1;
         }
         else {
            return i+1 % max;
         }
     }

  }

Output: (command line args : Thank you Mario but our princess is in another castle)

 T0 : Thank
 T1 : you
 T2 : Mario
 T3 : but
 T0 : our
 T1 : princess
 T2 : is
 T3 : in
 T0 : another
 T1 : castle

comments powered by Disqus