import java.util.*; // Here, we use a circular array for our queue so that Producers can continue to Produce as long as the // buffer is not full. We add the variable size to indicate the number of elements currently in the // buffer. If size==10, the Producer must wait for a consumer. public class ProducerConsumer4 { private static int[] buffer; private static int front, rear, size; private static Random g; private static Thread t1, t2, t3, t4, t5; private static boolean done; public static void main(String[] args) { buffer=new int[10]; front=0; rear=0; size=0; g=new Random(); done=false; Producer p1=new Producer("Producer 1"); Producer p2=new Producer("Producer 2"); Consumer c1=new Consumer("Consumer 1"); Consumer c2=new Consumer("Consumer 2"); Consumer c3=new Consumer("Consumer 3"); t1=new Thread(p1); t2=new Thread(p2); t3=new Thread(c1); t4=new Thread(c2); t5=new Thread(c3); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); } public static class Producer implements Runnable { private String name; public Producer(String name){ this.name=name; } public void run() { int temp; while(!done) // back to an infinite loop, let the threads run indefinitely { try{ temp=g.nextInt(1000); System.out.println(name + " sleeping for " + temp); Thread.sleep(temp); } catch(InterruptedException e) { System.out.println(e); } if(size>=10) { // cannot produce any more, would cause an Exception, cause Producer to wait System.out.println(name + " attempting to produce full buffer, forced to wait"); try{ temp=g.nextInt(3)+1; if(temp==1) t3.join(); else if(temp==2) t4.join(); else t5.join(); } catch(InterruptedException e) { System.out.println(e); } } else { // otherwise, room to produce so do so temp=g.nextInt(10)+1; System.out.println("Producer " + name + " producing " + temp + " at " + rear); buffer[rear]=temp; rear++; if(rear==10) rear=0; // circular array, circle back around size++; } } } } public static class Consumer implements Runnable { private String name; public Consumer(String name){ this.name=name; } public void run() { int temp; while(!done) // back to an infinite loop, let the threads run indefinitely { try{ temp=g.nextInt(1000); System.out.println(name + " sleeping for " + temp); Thread.sleep(temp); } catch(InterruptedException e) { System.out.println(e); } if(size==0) { // change this condition, cannot consume if size is not > 0 System.out.println(name + " attempting to consume empty list, forced to wait"); try{ if(g.nextInt(2)==1) t1.join(); else t2.join(); } catch(InterruptedException e) { System.out.println(e); } } temp=buffer[front]; System.out.println("Consumer " + name + " consuming " + temp + " at " + front); front++; if(front==10) front=0; size--; } } } } // This version is corrects the earlier problems. Run it for awhile though and we will find that all // producers are joining consumers and consumers are joining producers resulting in a "deadlock" situation. // Another issue is that between accessing the buffer and changing size, a Thread could be interrupted. // If that were to happen, we could potentially have another Thread operate using the previous value of size, // leading to a problem. In order to solve this, we need to ensure that the code between accessing the buffer, // changing front/rear and changing size is "synchronized". We might move that code from both the Producer and // Consumer into two separate methods, a "produceNow" method and a "consumeNow" method, both of which would be // synchronized.