Consider a simulation with three smoker threads and one agent thread. Each smoker continuously makes a cigarette and smokes it. But to make a cigarette, a smoker needs three ingredients: tobacco, paper, and matches. One of the smoker threads has only paper, another has only tobacco, and the third has only matches. The agent thread has an infinite supply of all three materials. The three smoker threads are initially blocked. The agent places two randomly chosen (different) ingredients on the table and unblocks the one smoker who has the remaining ingredient. The agent then blocks. The unblocked smoker removes the two ingredients from the table, makes a cigarette, and smokes it for a random amount of time, unblocking the agent on completion of smoking the cigarette. The agent then puts out another random two of the three ingredients, and the cycle repeats.
Write a multi-class multithreaded Java program that uses a monitor to synchronize the agent thread and the three smoker threads.
Do not mechanically translate semaphore code into monitor code! The agent thread executes in an agent object created from an agent class.
Each smoker thread executes in a smoker object. All smoker objects are created from one smoker class whose constructor is used to specify the ingredient possessed by the smoker object.
A driver class with a main method constructs the objects and starts the threads.
Use a single monitor object instantiated from a class Control for synchronization. Each of the four threads invokes a synchronized monitor method for its synchronization.
No semaphores are allowed. No synchronized blocks are allowed, only synchronized methods. No busy waiting is allowed. No calls to nap inside a synchronized method are allowed (do not nap while holding the monitor object's lock, that is, while inside a synchronized method or while inside a method called by a synchronized method).