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

public class ProducerConsumerLock2 {
    public static void main(String[] args) throws Exception {
        CubbyHole c = new CubbyHole();
        
        Producer p1 = new Producer(c, 1);
        Producer p2 = new Producer(c, 2);
        Consumer c1 = new Consumer(c, 1);
        Consumer c2 = new Consumer(c, 2);

        p1.start();
        p2.start();
        
        c1.start();
        c2.start();

        p1.join();
        p2.join();
        
        System.out.println("\r\nAll producers have completed");
        System.out.println("<enter> to exit");
        System.in.read();
        
        c1.interrupt();
        c2.interrupt();
        
    }

    public static class Producer extends Thread {
        private CubbyHole cubbyhole;
        private int id;
                    
        public Producer(CubbyHole c, int id) {
            super("P"+id);
            cubbyhole = c;
            this.id = id;
        }

        public void run() {
            for (int i = 0; i < 10; i++) {
                try {
                    int value = id*100 + i;
                    cubbyhole.put(value);
                    sleep((int)(Math.random() * 200));
                } catch (InterruptedException ex) { 
                    System.out.format("%s %s%n", Thread.currentThread().getName(), ex);  
                    return;
                }
            }
        }
    }

    public static class Consumer extends Thread {
        private CubbyHole cubbyhole;
        private int id;
            
        public Consumer(CubbyHole c, int id) {
            super("C"+id);
            cubbyhole = c;
            this.id = id;
        }

        public void run() {
            int count = 0;
            for (;;) {
                try {
                    int value = cubbyhole.get();
                    count += 1;
                    sleep((int)(Math.random() * 100));
               
                } catch (InterruptedException ex) { 
                    System.out.format("%s %s%n", Thread.currentThread().getName(), ex);  
                    return;
                }        
            }
        }
    }
    
  public static class CubbyHole {
      private int content;
      private boolean full = false;
      private Lock aLock = new ReentrantLock();
      private Condition prodCond = aLock.newCondition();
      private Condition consCond = aLock.newCondition();

      public int get() throws InterruptedException {
          aLock.lock();
          
          try {
              while (! full) {
                  try {
                      System.out.format("%s waits%n", Thread.currentThread().getName());  
                      consCond.await();
                      System.out.format("%s wakes%n", Thread.currentThread().getName());  
                  } catch (InterruptedException ex) { 
                      throw ex;
                  }
              }

              full = false;
              //prodCond.signalAll();
              prodCond.signal();
              System.out.format("%s gets: %d%n", Thread.currentThread().getName(), content);  
              return content;
          
          } finally {
              aLock.unlock();
          }
      }

      public void put(int value) throws InterruptedException {
          aLock.lock();
          
          try {
              while (full) {
                  try {
                      System.out.format("%s waits%n", Thread.currentThread().getName());  
                      prodCond.await();
                      System.out.format("%s wakes%n", Thread.currentThread().getName());  
                  } catch (InterruptedException ex) { 
                      throw ex;
                  }
              }

              content = value;
              full = true;
              //consCond.signalAll();
              consCond.signal();
              System.out.format("%s puts: %d%n", Thread.currentThread().getName(), value);  
          
          } finally {
              aLock.unlock();
          }
      }
  }
    
}

