// // (Somewhat) simple program demonstrating the use of Java threads // facilities to implement a "bounded buffer" of size N, with P // producer threads and C consumer threads. // Each producer thread will insert into the buffer M "messages" // (pairs of integers); consumer threads will read from the buffer // and print. // Consumer threads count how many buffer elements have been // consumed in all. // Main program waits for producers to finish and for the above // count to become zero, upon which it cancels the consumers. // // Command-line arguments are N, P, C, and M. // // ---- class containing main program -------------------------------- class ThreadsProducersConsumers1 { public static void main(String[] args) { // Process command-line arguments. if (args.length < 4) { System.out.println("Usage: java ThreadsProducersConsumers1 " + "buffSize numProducers numConsumers " + "numMessages"); System.exit(1); } int N = Integer.parseInt(args[0]); int P = Integer.parseInt(args[1]); int C = Integer.parseInt(args[2]); int M = Integer.parseInt(args[3]); // Create bounded buffer, counter. BoundedBuffer buff = new BoundedBuffer(N); CountDown count = new CountDown(P*M); // Create producer, consumer threads. Thread[] pthreads = new Thread[P]; for (int i = 0; i < P; i++) pthreads[i] = new Thread(new ProducerThreadObj(i, M, buff)); Thread[] cthreads = new Thread[C]; for (int i = 0; i < C; i++) cthreads[i] = new Thread(new ConsumerThreadObj(i, buff, count)); // Start up all the threads. for (int i = 0; i < P; i++) pthreads[i].start(); for (int i = 0; i < C; i++) cthreads[i].start(); // Wait for producer threads to finish. for (int i = 0; i < P; i++) { try { pthreads[i].join(); } catch (InterruptedException e) { // can't happen, but we have to humor the compiler } } System.out.println("Producer threads finished"); // Wait for all messages to be consumed. try { count.waitForZero(); } catch (InterruptedException e) { // can't happen, but we have to humor the compiler } // Interrupt the consumer threads. for (int i = 0; i < C; i++) cthreads[i].interrupt(); // Wait for consumer threads to finish. for (int i = 0; i < C; i++) { try { cthreads[i].join(); } catch (InterruptedException e) { // can't happen, but we have to humor the compiler } } System.out.println("Consumer threads finished"); } } // ---- class for pairs of integers ---------------------------------- // A teeny-tiny class to stand in for C++'s pair. class TwoIntegers { // Instance variables (like C++ member variables). public int first; public int second; // Constructor. public TwoIntegers(int first, int second) { this.first = first; this.second = second; } } // ---- class for bounded buffers ------------------------------------ // To be (mostly) consistent with the C++ example, we'll have it store // pairs of integers. class BoundedBuffer { // Instance variables (member variables in C++). private int bufferSize; private TwoIntegers[] buffer; // Implement as a circular array. private int nextToRemove; private int numElements; // Constructor. public BoundedBuffer(int bufferSize) { this.bufferSize = bufferSize; buffer = new TwoIntegers[bufferSize]; nextToRemove = 0; numElements = 0; } // Instance methods (like C++ member functions). // Add a message to the buffer (blocking if it's already full). // "synchronized" ensures that no other thread can call put/get // at the same time. public synchronized void put(TwoIntegers t) throws InterruptedException { // Delay if no room in buffer. // (Use "while", not "if" -- to be safe we should recheck // numElements when we get waked up from wait().) while (numElements == bufferSize) { wait() ; } // Add element. int next = (nextToRemove + numElements) % bufferSize; buffer[next] = t; numElements++; // Wake up all waiting threads, in case some are waiting in get(). notifyAll(); } // Remove and return a message from the buffer (blocking if it's // empty). "synchronized" ensures that no other thread can call // put/get at the same time. public synchronized TwoIntegers get() throws InterruptedException { // Delay if nothing in buffer. // (Use "while", not "if" -- to be safe we should recheck // numElements when we get waked up from wait().) while (numElements == 0) { wait(); } // Remove message TwoIntegers rVal = buffer[nextToRemove]; nextToRemove = (nextToRemove + 1) % bufferSize; --numElements; // Wake up all waiting threads, in case some are waiting in put(). notifyAll(); return rVal; } } // ---- class for producer threads ----------------------------------- // We'll have one object of this class for each producer thread. // It holds the data needed by the thread, plus the code. class ProducerThreadObj implements Runnable { // Instance variables. private int myID; private int M; // number of messages to produce private BoundedBuffer buff; // Constructor. public ProducerThreadObj(int myID, int M, BoundedBuffer buff) { this.myID = myID; this.M = M; this.buff = buff; } // This method implements run() in the Runnable interface // to define the behavior of a thread using this object. // It inserts M messages into the buffer. public void run() { for (int i = 0; i < M; ++i) { try { buff.put(new TwoIntegers(myID, i)); } catch (InterruptedException e) { // can't happen, but we have to humor the compiler } } } } // ---- class for consumer threads ----------------------------------- // We'll have one object of this class for each consumer thread. // It holds the data needed by the thread, plus the code. class ConsumerThreadObj implements Runnable { // Instance variables. private int myID; private BoundedBuffer buff; private CountDown count; // number of messages left to // consume // Constructor. public ConsumerThreadObj(int myID, BoundedBuffer buff, CountDown count) { this.myID = myID; this.buff = buff; this.count = count; } // Instance methods. // This method implements run() in the Runnable interface // to define the behavior of a thread using this object. // It removes messages from the buffer, prints them, and // updates the counter. // The main program will interrupt this thread when the // counter becomes zero. public void run() { while (true) { try { TwoIntegers msg = buff.get(); // We assume println() is thread-safe. Seems // reasonable. System.out.println("consumer " + myID + " processing request " + msg.second + " from producer " + msg.first); count.decrement(); } catch (InterruptedException e) { // interrupted -- time to quit return; } } } } // ---- class for shared counter ------------------------------------- // An instance of this class will hold the count of messages left to // consume, and allow us to wait until the value becomes zero. class CountDown { // Instance variables. private int count; // Constructor. CountDown(int count) { this.count = count; } // Instance methods -- synchronized so only one thread at a // time can change. // Method to decrement counter, and wake up waiting threads when // it becomes zero. public synchronized void decrement() { --count; if (count == 0) notifyAll(); } // Method to wait for counter to become zero. public synchronized void waitForZero() throws InterruptedException { while (count > 0) { wait(); } } }