Showing posts with label Concurrency. Show all posts
Showing posts with label Concurrency. Show all posts

Tuesday, August 5, 2014

DelayQueue Usage : Greenhouse Simulation

Thinking In Java Ex33 [Concurrency] :

Q. Modify GreenhouseScheduler.java so that it uses a DelayQueue instead of a ScheduledExecutor.

What we need to do is to know the duration (total no of times the task needs to execute) and the interval (time after which the task executes). So that we add that task to the queue duration/interval number of times, setting the delay time to be interval * (n+1) //n is the iteration number counter when the object is being added in a loop.

Code:

import java.util.concurrent.*;
import java.util.*; 

public class GreenhouseScheduler {

    private volatile boolean light = false;
    private volatile boolean water = false;
    private String thermostat = "Day";

    public synchronized String getThermostat() {
        return thermostat;
    }

    public synchronized void setThermostat(String value) {
        thermostat = value;
    }

    public static DelayQueue<Delayed> queue = new DelayQueue<>();
//    ScheduledThreadPoolExecutor scheduler
//            = new ScheduledThreadPoolExecutor(10);

    public static void schedule(Delayed event) {
        queue.add(event);
        //  scheduler.schedule(event, delay, TimeUnit.MILLISECONDS);
    }

    public static void repeat(Task event, long interval, long duration) {
        if (interval <= duration) {
            for (int i = 0; i < duration / interval; i++) {
                Task t = event.create(interval * (i + 1));
                queue.put(t);
            }
        }
    }

    abstract class Task implements Delayed {

        private long delay;
        private long trigger;

        Task(long delay) {
            this.delay = TimeUnit.NANOSECONDS.convert(delay, TimeUnit.MILLISECONDS);
            trigger = this.delay + System.nanoTime();
        }

        abstract Task create(long delay);

        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(trigger - System.nanoTime(), TimeUnit.NANOSECONDS);
        }

        @Override
        public int compareTo(Delayed o) {
            Task that = (Task) o;
            if (trigger < that.trigger) {
                return -1;
            } else if (trigger > that.trigger) {
                return 1;
            } else {
                return 0;
            }
        }
    }

    class LightOn extends Task implements Runnable {

        LightOn() {
            super(0);
        }

        LightOn(long delay) {
            super(delay);
        }

        LightOn create(long delay) {
            return new LightOn(delay);
        }

        public void run() {
            // Put hardware control code here to
            // physically turn on the light.
            System.out.println("Turning on lights");
            light = true;
        }
    }

    class LightOff extends Task implements Runnable {

        LightOff() {
            super(0);
        }

        LightOff(long delay) {
            super(delay);
        }

        LightOff create(long delay) {
            return new LightOff(delay);
        }

        public void run() {
            // Put hardware control code here to
            // physically turn off the light.
            System.out.println("Turning off lights");
            light = false;
        }
    }

    class WaterOn extends Task implements Runnable {

        WaterOn() {
            super(0);
        }

        WaterOn(long delay) {
            super(delay);
        }

        WaterOn create(long delay) {
            return new WaterOn(delay);
        }

        public void run() {
            // Put hardware control code here.
            System.out.println("Turning greenhouse water on");
            water = true;
        }
    }

    class WaterOff extends Task implements Runnable {

        WaterOff() {
            super(0);
        }

        WaterOff(long delay) {
            super(delay);
        }

        WaterOff create(long delay) {
            return new WaterOff(delay);
        }

        public void run() {
            // Put hardware control code here.
            System.out.println("Turning greenhouse water off");
            water = false;
        }
    }

    class ThermostatNight extends Task implements Runnable {

        ThermostatNight() {
            super(0);
        }

        ThermostatNight(long delay) {
            super(delay);
        }

        ThermostatNight create(long delay) {
            return new ThermostatNight(delay);
        }

        public void run() {
            // Put hardware control code here.
            System.out.println("Thermostat to night setting");
            setThermostat("Night");
        }
    }

    class ThermostatDay extends Task implements Runnable {

        ThermostatDay() {
            super(0);
        }

        ThermostatDay(long delay) {
            super(delay);
        }

        ThermostatDay create(long delay) {
            return new ThermostatDay(delay);
        }

        public void run() {
            // Put hardware control code here.
            System.out.println("Thermostat to day setting");
            setThermostat("Day");
        }
    }

    class Bell extends Task implements Runnable {

        Bell() {
            super(0);
        }

        Bell(long delay) {
            super(delay);
        }

        Bell create(long delay) {
            return new Bell(delay);
        }

        public void run() {
            System.out.println("Bing!");
        }
    }

    class Terminate extends Task implements Runnable {

        Terminate() {
            super(0);
        }

        Terminate(long delay) {
            super(delay);
        }

        Terminate create(long delay) {
            return new Terminate(delay);
        }

        public void run() {
            System.out.println("Terminating");
            queue.clear();
            // Must start a separate task to do this job,
            // since the scheduler has been shut down:
//            new Thread() {
//                public void run() {
            for (DataPoint d : data) {
                System.out.println(d);
            }
//                }
//            }.start();
        }
    }
    // New feature: data collection

    static class DataPoint {

        final Calendar time;
        final float temperature;
        final float humidity;

        public DataPoint(Calendar d, float temp, float hum) {
            time = d;
            temperature = temp;
            humidity = hum;
        }

        public String toString() {
            return time.getTime()
                    + String.format(
                            " temperature: %1$.1f humidity: %2$.2f",
                            temperature, humidity);
        }
    }
    private Calendar lastTime = Calendar.getInstance();

    { // Adjust date to the half hour
        lastTime.set(Calendar.MINUTE, 30);
        lastTime.set(Calendar.SECOND, 00);
    }
    private float lastTemp = 65.0f;
    private int tempDirection = +1;
    private float lastHumidity = 50.0f;
    private int humidityDirection = +1;
    private Random rand = new Random(47);
    List<DataPoint> data = Collections.synchronizedList(
            new ArrayList<DataPoint>());

    class CollectData extends Task implements Runnable {

        CollectData() {
            super(0);
        }

        CollectData create(long delay) {
            return new CollectData(delay);
        }

        CollectData(long delay) {
            super(delay);
        }

        public void run() {
            System.out.println("Collecting data");
            synchronized (GreenhouseScheduler.this) {
                // Pretend the interval is longer than it is:
                lastTime.set(Calendar.MINUTE,
                        lastTime.get(Calendar.MINUTE) + 30);
                // One in 5 chances of reversing the direction:
                if (rand.nextInt(5) == 4) {
                    tempDirection = -tempDirection;
                }
                // Store previous value:
                lastTemp = lastTemp
                        + tempDirection * (1.0f + rand.nextFloat());
                if (rand.nextInt(5) == 4) {
                    humidityDirection = -humidityDirection;
                }
                lastHumidity = lastHumidity
                        + humidityDirection * rand.nextFloat();
                // Calendar must be cloned, otherwise all
                // DataPoints hold references to the same lastTime.
                // For a basic object like Calendar, clone() is OK.
                data.add(new DataPoint((Calendar) lastTime.clone(), lastTemp, lastHumidity));
            }
        }
    }

    public static void main(String[] args) throws Exception {
        GreenhouseScheduler gh = new GreenhouseScheduler();

        repeat(gh.new Bell(), 1000, 4000);
        repeat(gh.new ThermostatNight(), 2000, 4000);
        repeat(gh.new LightOn(), 200, 4000);
        repeat(gh.new LightOff(), 400, 4000);
        repeat(gh.new WaterOn(), 600, 4000);
        repeat(gh.new WaterOff(), 800, 4000);
        repeat(gh.new ThermostatDay(), 1400, 4000);
        repeat(gh.new CollectData(), 500, 4000);
        queue.put(gh.new Terminate(5000));

        while (!queue.isEmpty()) {
            Runnable r = (Runnable) queue.take();
            r.run();
        }
    }
}

Output:

Turning on lights
Turning on lights
Turning off lights
Collecting data
Turning on lights
Turning greenhouse water on
Turning on lights
Turning off lights
Turning greenhouse water off
Bing!
Turning on lights
Collecting data
Turning on lights
Turning off lights
Turning greenhouse water on
Turning on lights
Thermostat to day setting
Collecting data
Turning on lights
Turning off lights
Turning greenhouse water off
Turning on lights
Turning greenhouse water on
Bing!
Thermostat to night setting
Turning on lights
Turning off lights
Collecting data
Turning on lights
Turning on lights
Turning off lights
Turning greenhouse water on
Turning greenhouse water off
Collecting data
Turning on lights
Turning on lights
Turning off lights
Thermostat to day setting
Bing!
Turning on lights
Turning greenhouse water on
Collecting data
Turning on lights
Turning off lights
Turning greenhouse water off
Turning on lights
Collecting data
Turning on lights
Turning off lights
Turning greenhouse water on
Turning on lights
Bing!
Thermostat to night setting
Turning on lights
Turning off lights
Turning greenhouse water off
Collecting data
Terminating
Tue Aug 05 09:00:00 IST 2014 temperature: 66.4 humidity: 50.05
Tue Aug 05 09:30:00 IST 2014 temperature: 68.0 humidity: 50.47
Tue Aug 05 10:00:00 IST 2014 temperature: 69.7 humidity: 51.42
Tue Aug 05 10:30:00 IST 2014 temperature: 70.8 humidity: 50.87
Tue Aug 05 11:00:00 IST 2014 temperature: 72.0 humidity: 50.32
Tue Aug 05 11:30:00 IST 2014 temperature: 73.2 humidity: 49.92
Tue Aug 05 12:00:00 IST 2014 temperature: 71.9 humidity: 49.81
Tue Aug 05 12:30:00 IST 2014 temperature: 70.1 humidity: 50.25

Thursday, July 17, 2014

Dining Philosophers Problem [Code] : [Java Concurrency]

The dining philosophers problem, invented by Edsger Dijkstra, is the classic demonstration of deadlock. The basic description specifies five philosophers (but the example shown here will allow any number). These philosophers spend part of their time thinking and part of their time eating. While they are thinking, they don’t need any shared resources, but they eat using a limited number of utensils. In the original problem description, the utensils are forks, and two forks are required to get spaghetti from a bowl in the middle of the table, but it seems to make more sense to say that the utensils are chopsticks. Clearly, each philosopher will require two chopsticks in order to eat.
A difficulty is introduced into the problem: As philosophers, they have very little money, so they can only afford five chopsticks (more generally, the same number of chopsticks as philosophers). These are spaced around the table between them. When a philosopher wants to eat, that philosopher must pick up the chopstick to the left and the one to the right. If the philosopher on either side is using a desired chopstick, our philosopher must wait until the necessary chopsticks become available.

The code below has a possibility of deadlock. If every philosopher picks the chopstick to the right of him the last philosopher won't be able to pick his right chopstick, thus no one is able to pick his left and eat food, waiting on each other in a chain to get the second chopstick.

Code below taken from Thinking In Java 4th Edition p1224 onwards]

import java.util.concurrent.*;
import java.util.*;

class Chopstick {

    private boolean taken = false;

    public synchronized
            void take() throws InterruptedException {
        while (taken) {
            wait();
        }
        taken = true;
    }

    public synchronized void drop() {
        taken = false;
        notifyAll();
    }
}

class Philosopher implements Runnable {

    private Chopstick left;
    private Chopstick right;
    private final int id;
    private final int ponderFactor;
    private Random rand = new Random(47);

    private void pause() throws InterruptedException {
        if (ponderFactor == 0) {
            return;
        }
        TimeUnit.MILLISECONDS.sleep(
                rand.nextInt(ponderFactor * 250));
    }

    public Philosopher(Chopstick left, Chopstick right,
            int ident, int ponder) {
        this.left = left;
        this.right = right;
        id = ident;
        ponderFactor = ponder;
    }

    public void run() {
        try {
            while (!Thread.interrupted()) {
                System.out.println(this + " " + "thinking");
                pause();
                // Philosopher becomes hungry
                System.out.println(this + " " + "grabbing right");
                right.take();
                System.out.println(this + " " + "grabbing left");
                left.take();
                System.out.println(this + " " + "eating");
                pause();
                right.drop();
                left.drop();
            }
        } catch (InterruptedException e) {
            System.out.println(this + " " + "exiting via interrupt");
        }
    }

    public String toString() {
        return "Philosopher " + id;
    }
}

public class DeadlockingDiningPhilosophers {

    public static void main(String[] args) throws Exception {
        int ponder = 5;
        if (args.length > 0) {
            ponder = Integer.parseInt(args[0]);
        }
        int size = 5;
        if (args.length > 1) {
            size = Integer.parseInt(args[1]);
        }
        ExecutorService exec = Executors.newCachedThreadPool();
        Chopstick[] sticks = new Chopstick[size];
        for (int i = 0; i < size; i++) {
            sticks[i] = new Chopstick();
        }
        for (int i = 0; i < size; i++) {
            exec.execute(new Philosopher(
                    sticks[i], sticks[(i + 1) % size], i, ponder));
        }
        if (args.length == 3 && args[2].equals("timeout")) {
            TimeUnit.SECONDS.sleep(5);
        } else {
            System.out.println("Press 'Enter' to quit");
            System.in.read();
        }
        exec.shutdownNow();
    }
}

You can set the "ponder" variable to 0 to see the deadlock occuring fast. For a deadlock to occur, the following four conditions must be met: 1. Mutual exclusion. At least one resource used by the tasks must not be shareable. In this case, a Chopstick can be used by only one Philosopher at a time. 2. At least one task must be holding a resource and waiting to acquire a resource currently held by another task. That is, for deadlock to occur, a Philosopher must be holding one Chopstick and waiting for another one. 3. A resource cannot be preemptively taken away from a task. Tasks only release resources as a normal event. Our Philosophers are polite and they don’t grab Chopsticks from other Philosophers. 4. A circular wait can happen, whereby a task waits on a resource held by another task, which in turn is waiting on a resource held by another task, and so on, until one of the tasks is waiting on a resource held by the first task, thus gridlocking everything. In this example, the circular wait happens because each Philosopher tries to get the right Chopstick first and then the left. Now to make the code deadlock free we can make the last philosopher get the left chopstick first and then the right chopstick so that the circular chain is broken. The deadlock never occurs now. There are many other ways for avoiding the deadlock in this case, this is just one of them.

 Code:

import java.util.concurrent.*;
import java.util.*;

class Chopstick {

    private boolean taken = false;

    public synchronized
            void take() throws InterruptedException {
        while (taken) {
            wait();
        }
        taken = true;
    }

    public synchronized void drop() {
        taken = false;
        notifyAll();
    }
}

class Philosopher implements Runnable {

    private Chopstick left;
    private Chopstick right;
    private final int id;
    private final int ponderFactor;
    private Random rand = new Random(47);

    private void pause() throws InterruptedException {
        if (ponderFactor == 0) {
            return;
        }
        TimeUnit.MILLISECONDS.sleep(
                rand.nextInt(ponderFactor * 250));
    }

    public Philosopher(Chopstick left, Chopstick right,
            int ident, int ponder) {
        this.left = left;
        this.right = right;
        id = ident;
        ponderFactor = ponder;
    }

    public void run() {
        try {
            while (!Thread.interrupted()) {
                System.out.println(this + " " + "thinking");
                pause();
// Philosopher becomes hungry
                System.out.println(this + " " + "grabbing right");
                right.take();
                System.out.println(this + " " + "grabbing left");
                left.take();
                System.out.println(this + " " + "eating");
                pause();
                right.drop();
                left.drop();
            }
        } catch (InterruptedException e) {
            System.out.println(this + " " + "exiting via interrupt");
        }
    }

    public String toString() {
        return "Philosopher " + id;
    }
}

public class FixedDiningPhilosophers {

    public static void main(String[] args) throws Exception {
        int ponder = 5;
        if (args.length > 0) {
            ponder = Integer.parseInt(args[0]);
        }
        int size = 5;
        if (args.length > 1) {
            size = Integer.parseInt(args[1]);
        }
        ExecutorService exec = Executors.newCachedThreadPool();
        Chopstick[] sticks = new Chopstick[size];
        for (int i = 0; i < size; i++) {
            sticks[i] = new Chopstick();
        }
        for (int i = 0; i < size; i++) {
            if (i < (size - 1)) {
                exec.execute(new Philosopher(
                        sticks[i], sticks[i + 1], i, ponder));
            } else {
                exec.execute(new Philosopher(
                        sticks[0], sticks[i], i, ponder));
            }
        }
        if (args.length == 3 && args[2].equals("timeout")) {
            TimeUnit.SECONDS.sleep(5);
        } else {
            System.out.println("Press 'Enter' to quit");
            System.in.read();
        }
        exec.shutdownNow();
    }
}

Exercise: Change DeadlockingDiningPhilosophers.java so that when a philosopher is done with its chopsticks, it drops them into a bin. When a philosopher wants to eat, it takes the next two available chopsticks from the bin. Does this eliminate the possibility of deadlock? Can you reintroduce deadlock by simply reducing the number of available chopsticks?
My solution:

import java.util.concurrent.*;
import java.util.*;
import static net.mindview.util.Print.*;

class Chopstick {

    private boolean taken = false;

    public synchronized void take() throws InterruptedException {
        while (taken) {
            wait();
        }
        taken = true;
    }

    public synchronized void drop() {
        taken = false;
        notifyAll();
    }
}

class Bin {

    BlockingQueue bin = new LinkedBlockingQueue<>();

    public void put(Chopstick stick) throws InterruptedException {
        bin.put(stick);
    }

    public Chopstick get() throws InterruptedException {
        return bin.take();
    }
}

class Philosopher implements Runnable {

    private Chopstick left;
    private Chopstick right;
    private LinkedBlockingQueue bin;
    private final int id;
    private final int ponderFactor;
    private Random rand = new Random(47);

    private void pause() throws InterruptedException {
        if (ponderFactor == 0) {
            return;
        }
        TimeUnit.MILLISECONDS.sleep(rand.nextInt(ponderFactor * 250));
    }

    public Philosopher(Chopstick left, Chopstick right,
            LinkedBlockingQueue bin, int ident, int ponder) {
        this.left = left;
        this.right = right;
        this.bin = bin;
        id = ident;
        ponderFactor = ponder;
    }

    public void run() {
        try {
            while (!Thread.interrupted()) {
                print(this + " " + "thinking");
                pause();
// Philosopher becomes hungry
                print(this + " taking first, right chopstick");
                right = bin.take();
                print(this + " taking second, left chopstick");
                left = bin.take();
                print(this + " eating");
                pause();
                print(this + " returning chopsticks");
                bin.put(right);
                bin.put(left);
            }
        } catch (InterruptedException e) {
            print(this + " " + "exiting via interrupt");
        }
    }

    public String toString() {
        return "Philosopher " + id;
    }
}

public class DeadlockingDiningPhilosophers {

    public static void main(String[] args) throws Exception {
        int ponder = 0;
        if (args.length > 0) {
            ponder = Integer.parseInt(args[0]);
        }
        int size = 5;
        if (args.length > 1) {
            size = Integer.parseInt(args[1]);
        }
        ExecutorService exec = Executors.newCachedThreadPool();
// chopstick bin:
        LinkedBlockingQueue bin = new LinkedBlockingQueue<>();
        Chopstick[] sticks = new Chopstick[size];
        for (int i = 0; i < size; i++) {
            sticks[i] = new Chopstick();
            bin.put(sticks[i]);
        }
        for (int i = 0; i < size; i++) {
            exec.execute(new Philosopher(sticks[i], sticks[(i + 1) % size], bin, i, ponder));
        }
        if (args.length == 3 && args[2].equals("timeout")) {
            TimeUnit.SECONDS.sleep(5);
        } else {
            System.out.println("Press 'Enter' to quit");
            System.in.read();
        }
        exec.shutdownNow();
    }
}

Does this eliminate the possibility of deadlock? No. Consider the case when each philosopher takes a single chopstick from the bin so that now the bin contains 0 chopsticks. Nobody has the second chopstick to eat the spaghetti.

Wednesday, July 16, 2014

Java Multithreading : Using Lock and Condition Objects : Restaurant Simulation Example Code [ Concurrency ]

Consider a restaurant that has one chef and one waitperson. The waitperson must wait for the chef to prepare a meal. When the chef has a meal ready, the chef notifies the waitperson, who then gets and delivers the meal and goes back to waiting. After the meal is delivered, the waitperson should notify the BusBoy (new Class) to clean up. This is an example of task cooperation: The chef represents the producer, and the waitperson represents the consumer. Both tasks must handshake with each other as meals are produced and consumed, and the system must shut down in an orderly fashion. Use explicit Lock and Condition objects. Here is the story modeled in code:

[Note: This is the solution to questions taken from Thinking In Java 4th Edition p1212 and p1215]

Code:

import java.util.concurrent.*;
import java.util.concurrent.locks.*;

class BusBoy implements Runnable {

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    Restaurant restaurant;

    BusBoy(Restaurant r) {
        restaurant = r;
    }

    public void run() {
        try {
            while (!Thread.interrupted()) {

                lock.lock();
                try {
                    condition.await();
                    System.out.println("BusBoy is Cleaning up!\n");
                } finally {
                    lock.unlock();
                }
            }
        } catch (InterruptedException e) {
            System.out.println("BusBoy interrupted!");
        }
    }

}

class Meal {

    private final int orderNum;
    volatile int cleanUp = 0;

    public Meal(int orderNum) {
        this.orderNum = orderNum;
    }

    public String toString() {
        return "Meal " + orderNum;
    }
}

class WaitPerson implements Runnable {

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    private Restaurant restaurant;

    public WaitPerson(Restaurant r) {
        restaurant = r;
    }

    public void run() {
        try {
            while (!Thread.interrupted()) {
                lock.lock();
                try {
                    while (restaurant.meal == null) {
                        condition.await(); //... for the chef to produce a meal
                    }
                } finally {
                    lock.unlock();
                }
                System.out.println("Waitperson got " + restaurant.meal);

                restaurant.chef.lock.lock();
                try {
                    restaurant.meal = null;
                    System.out.println("Meal taken by the waitperson!");
                    restaurant.chef.condition.signalAll(); //Ready for another
                } finally {
                    restaurant.chef.lock.unlock();
                }

                try {
                    restaurant.boy.lock.lock();
                    System.out.println("Notifying BusBoy to cleanup...");
                    restaurant.boy.condition.signalAll();
                } finally {
                    restaurant.boy.lock.unlock();
                }
            }
        } catch (InterruptedException e) {
            System.out.println("WaitPerson interrupted!");
        }
    }
}

class Chef implements Runnable {

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    private Restaurant restaurant;
    private int count = 0;

    public Chef(Restaurant r) {
        restaurant = r;
    }

    public void run() {
        try {
            while (!Thread.interrupted()) {
                lock.lock();
                try {

                    while (restaurant.meal != null) {
                        condition.await();//... for the meal to be taken
                    }
                } finally {
                    lock.unlock();
                }

                if (++count == 10) {
                    System.out.println("Out of food, closing");
                    restaurant.exec.shutdownNow();
                    return;
                }
                System.out.println("Order up!");
                restaurant.waitPerson.lock.lock();
                try {
                    restaurant.meal = new Meal(count);
                    restaurant.waitPerson.condition.signalAll();
                } finally {
                    restaurant.waitPerson.lock.unlock();
                }
                TimeUnit.MILLISECONDS.sleep(100);
            }
        } catch (InterruptedException e) {
            System.out.println("Chef interrupted!");
        }
    }
}

public class Restaurant {

    Meal meal;
    ExecutorService exec = Executors.newCachedThreadPool();
    WaitPerson waitPerson = new WaitPerson(this);
    Chef chef = new Chef(this);
    BusBoy boy = new BusBoy(this);

    public Restaurant() {
        exec.execute(chef);
        exec.execute(waitPerson);
        exec.execute(boy);
    }

    public static void main(String[] args) {
        new Restaurant();
    }

}

Output:

Order up!
Waitperson got Meal 1
Meal taken by the waitperson!
Notifying BusBoy to cleanup...
BusBoy is Cleaning up!

Order up!
Waitperson got Meal 2
Meal taken by the waitperson!
Notifying BusBoy to cleanup...
BusBoy is Cleaning up!

Order up!
Waitperson got Meal 3
Meal taken by the waitperson!
Notifying BusBoy to cleanup...
BusBoy is Cleaning up!

Order up!
Waitperson got Meal 4
Meal taken by the waitperson!
Notifying BusBoy to cleanup...
BusBoy is Cleaning up!

Order up!
Waitperson got Meal 5
Meal taken by the waitperson!
Notifying BusBoy to cleanup...
BusBoy is Cleaning up!

Order up!
Waitperson got Meal 6
Meal taken by the waitperson!
Notifying BusBoy to cleanup...
BusBoy is Cleaning up!

Order up!
Waitperson got Meal 7
Meal taken by the waitperson!
Notifying BusBoy to cleanup...
BusBoy is Cleaning up!

Order up!
Waitperson got Meal 8
Meal taken by the waitperson!
Notifying BusBoy to cleanup...
BusBoy is Cleaning up!

Order up!
Waitperson got Meal 9
Meal taken by the waitperson!
Notifying BusBoy to cleanup...
BusBoy is Cleaning up!

Out of food, closing
WaitPerson interrupted!
BusBoy interrupted!

Implementation of Infinite Buffer Producer-Consumer Problem using Java [Multi-Threading / Concurrency]

Constraints:

1. The producer should not put items into the buffer if the consumer has yet to consume an item to free
the circular buffer.

2. The consumer should not consume an item that has not been produced by the producer yet.

Code:

/*
        Author: Jatin Thakur
                coderbots.blogspot.com
*/
 
import java.util.Arrays;
import java.util.concurrent.*;
 
class Consumer implements Runnable {
 
    private final Scenario scenario;
 
    Consumer(Scenario s) {
        scenario = s;
        Arrays.fill(buffer, 0);
    }
 
    int buffer[] = new int[10];
 
    volatile int current = 0;
    volatile int total = 0;
 
    public void run() {
        try {
            while (!Thread.interrupted()) {
                synchronized (this) {
                    while (buffer[current] == 0) {
                        wait();
                    }
                }
                synchronized (scenario.producer) {
                    System.out.println("Consuming item: " + current);
                    buffer[current++] = 0;
 
                    ++total;
                    scenario.producer.notifyAll();
                }
                if (current == 10) {
                    current = 0;
                }
                Thread.yield();
            }
        } catch (InterruptedException e) {
            System.out.println("Consumer interrupted!");
 
        }
    }
}
 
class Producer implements Runnable {
 
    volatile int total = 0;
    private final Scenario scenario;
 
    Producer(Scenario s) {
        scenario = s;
    }
    volatile int current = 0;
 
    public void run() {
        try {
            while (!Thread.interrupted()) {
                synchronized (this) {
                    while (scenario.consumer.buffer[(current == 9 ? -1 : current) + 1] == 1) {
                        wait();
                    }
                }
                synchronized (scenario.consumer) {
                    System.out.println("Producing item: " + current);
                    scenario.consumer.buffer[current++] = 1;
                    scenario.consumer.notifyAll();
 
                    ++total;
                }
                if (current == 10) {
                    current = 0;
                }
                Thread.yield();
            }
        } catch (InterruptedException e) {
            System.out.println("Producer interrupted!");
        }
    }
}
 
public class Scenario {
 
    Consumer consumer = new Consumer(this);
    Producer producer = new Producer(this);
 
    ExecutorService exec = Executors.newCachedThreadPool();
 
    Scenario() {
        exec.execute(consumer);
        exec.execute(producer);
    }
 
    public static void main(String[] args) throws InterruptedException {
        Scenario sc = new Scenario();
        TimeUnit.MILLISECONDS.sleep(5);
        sc.exec.shutdownNow();
        System.out.println("Total items produced: " + sc.producer.total);
        System.out.println("Total items consumed: " + sc.consumer.total + "\n\n");
    }
}

Sample Run:

Producing item: 0
Producing item: 1
Producing item: 2
Producing item: 3
Producing item: 4
Producing item: 5
Producing item: 6
Producing item: 7
Producing item: 8
Consuming item: 0
Consuming item: 1
Consuming item: 2
Consuming item: 3
Consuming item: 4
Consuming item: 5
Consuming item: 6
Consuming item: 7
Consuming item: 8
Producing item: 9
Producing item: 0
Producing item: 1
Producing item: 2
Producing item: 3
Producing item: 4
Producing item: 5
Producing item: 6
Producing item: 7
Consuming item: 9
Consuming item: 0
Consuming item: 1
Consuming item: 2
Consuming item: 3
Consuming item: 4
Consuming item: 5
Consuming item: 6
Consuming item: 7
Producing item: 8
Producing item: 9
Consuming item: 8
Consuming item: 9
Producing item: 0
Producing item: 1
Producing item: 2
Producing item: 3
Producing item: 4
Producing item: 5
Producing item: 6
Producing item: 7
Producing item: 8
Consuming item: 0
Consuming item: 1
Consuming item: 2
Consuming item: 3
Consuming item: 4
Consuming item: 5
Consuming item: 6
Consuming item: 7
Producing item: 9
Total items produced: 30
Total items consumed: 28

If you find any errors, please do comment.