/**
 * Sandeep Desai s_desai@hotmail.com http://www.thedesai.net
 *
 * JavaThreads.java
 *
 * You can print this program and read it from top to bottom
 *
 * Book Reference: Thinking in Java 3rd Ed
 */

import java.util.*;

/**
 * One Call stack per thread
 * Threads run in any order for a given priority
 * Each thread will start and run to completion
 * Thread execution order and duration not gauranteed
 * once thread is dead it cannot be restarted
 * longest waiting thread may not get lock
 * JVM has its own thread scheduler,
 *
 *
 * java.lang.Thread methods
 *    // default name is Thread-0 and so on
 *    // main thread called main
 *    Thread(String name)
 *    Thread(Runnable target)
 *    Thread(Runnable target, String name)
 *
 *    Thread(ThreadGroup group, String name)
 *    Thread(ThreadGroup group, Runnable target)
 *    Thread(ThreadGroup group, Runnable target, String name)
 *
 *    start(), run(), isAlive(), setName(String name), getName()
 *    setPriority(int priority) // IllegalArgumentException at runtime if > 10
 *       // JVM does not gaurantee setPriority
 *    getPriority()
 *    join() throws InterruptedException,
 *    join(long milliseconds) throws InterruptedException,
 *    join(int nanos) throws InterruptedException,
 *    boolean isAlive() // false for new thread and dead thread
 *                      // true running state only
 *    void setDaemon(boolean b) // can only be set before start()
 *       // program runs till all daemon threads finished
 *       // (main thread can finish first)
 *    static boolean holdsLock(Object obj) // true if current thread holds lock
 *    static sleep(long milliseconds) throws InterruptedException,
 *    static sleep(long milliseconds, int nanos) throws InterruptedException,
 *    static yield()
 *    static int MIN_PRIORITY = 1 // setPriority(int)
 *    static int MAX_PRIORITY = 10
 *    static int NORM_PRIORITY = 5
 *
 * java.lang.Object
 *    wait(), throws InterruptedException, // releases locks
 *    wait(long timeout), wait(long timeout, int nanos)
 *    notify(), throws IllegalMonitorException (Runtime Exception)
 *    notifyAll() throws IllegalMonitorException (Runtime Exception)
 *      wait, notify must be called from syncrhonized code
 *
 *    wait() gives up lock
 *    notify(), join(), sleep(), yield() keep locks
 *
 * java.lang.Runnable { public void run() }
 *
 * To create thread extend Thread or Runnable
 * extending Runnable better approach
 * **********
 * synchronized only on method or block of code
 * synchronized on method does a lock on this so if thread in one syncrhonized
 *    method, no thread will enter another syncrhonized method
 * syncrhonized on static method does lock on object.class
 * syncrhonized(null)  nullpointer exception thrown
 * synchronized can be added or removed when overriding methods
 *
 * Thread States
 *
 *      Waiting/Blocking/Sleeping
 *           |           ^
 *           V           |
 * New -> Runnable -> Running -> Dead
 *
 * Runnable means start() has been called but thread is not yet
 *    in the run() method
 * It is waiting for the scheduler
 *
 *
 * when Thread.start() overridden Thread won't run unless super.start() called
 * Thread.setDaemon() can only be called before start()
 * Thread.setPriority() throws runtime exception if priority greater than MAX_PRIORITY
 * ThreadGroup can have common priority

 */
class JavaThreads  {

  static class MyRunnable implements Runnable {
    public void run() {
      System.out.println("Thread1 (MyRunnable) thread started");
      for (int i = 0; i < 5; i++) {
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
        System.out.println("Thread1 running=" + i);
      }
 
      System.out.println("Thread1 over");
    }
  }
 
  static class MyThread extends Thread {
    Thread t;
    public MyThread(Thread t) { this.t = t; }
    public void run() {
      System.out.println("Thread2 started");
      doSomething();
      // give another thread chance to run,
      // Scheduler may end up scheduling this thread itself
      yield();
      System.out.println("Waiting for thread1 to finish by calling join()");
      // locks current thread until t finishes
      try {
        // can also call t.join(int) which will give up after
        // specified number of miliseconds
        t.join();
      } catch (InterruptedException e) {}
      doSomething();
      // We wait for t to finish
 
      System.out.println("Thread2 over, CurrentThread="
                   + Thread.currentThread().getName());
    }
 
    // method will be executed by only one thread at a time
    // locks on this
    synchronized void doSomething() {
      // After sleep thread goes to Runnable state
      try { Thread.sleep(100); } catch (InterruptedException e) {}
    }
 
    // locks on MyThread.class
    static synchronized void doSomething2() {
      // After sleep thread goes to Runnable state
      try { Thread.sleep(100); } catch (InterruptedException e) {}
    }

    String s1 = "foo";
    String s2 = "foo";
 
    void doSomething3() {
      // lock on s only one thread will enter this code at a time
      synchronized(s1) {
 
      }
    }
 
    // This code will deadlock, which can happend any time
    // very hard to debug
    void deadlock1() {
      // lock on s only one thread will enter this code at a time
      synchronized(s1) {
        synchronized(s2) {
        }
      }
    }
 
    void deadlock2() {
      // lock on s only one thread will enter this code at a time
      synchronized(s2) {
        synchronized(s1) {
        }
      }
    }
  }
 
  static void thread1() {
    MyRunnable mr1 = new MyRunnable();
    //mr1.run(); // Legal call does not start therad
    Thread mt2 = new Thread(mr1);
    // setPriority not gauranteed
    mt2.setPriority(8); // between 1 to 10 (MIN_PRIORITY to MAX_PRIORITY)
    // IllegalArgumentException at runtime
    //mt2.setPriority(Thread.MAX_PRIORITY + 2);
    mt2.start();

    MyThread mt1 = new MyThread(mt2);
    mt1.start();
  }
 
  static class Thread3 extends Thread {
    int total;
    String s = "foo";
    public void run() {
      synchronized(this) {
        for (int i=0; i < 30; i++) {
          total += i;
          try { sleep(100); } catch (InterruptedException e) {}
        }
        notify();
        // this line will throw IllegalMonitorException since
        // we are not locked on s
        // s.notify();
        System.out.println(
          new Date() + " Thread3 over notify() all threads waiting on lock");
      }
    }
  }
 
  static void thread2() {
    Thread3 t = new Thread3();
    t.start();
    synchronized(t) {
      try {
        System.out.println(new Date() + " Wating for t to complete ");
        t.wait(); // this should wait approx 3 seconds
      } catch (InterruptedException e) {}
      System.out.println("Total is->" + t.total);
    }
  }
 
  static void thread3() {
    final SyncTest s = new SyncTest();
    Thread t1 = new Thread(new Runnable() {
      public void run() {
        s.method1();
      }
    });
    Thread t2 = new Thread(new Runnable() {
      public void run() {
        s.method2();
      }
    });
    t1.start();
    t2.start();
  }
 
  public static void main(String[] args) {
    thread2();
    thread1();
    thread3();
    Consumer.test();
  }
}

class SyncTest {
  public synchronized void method1() {
    for (int i = 0; i < 4; ++i) {
      try {
        Thread.sleep(1000);
        System.out.println("method1");
      } catch (InterruptedException e) {}
    }
  }
 
  public static synchronized void method2() {
    for (int i = 0; i < 4; ++i) {
      try {
        Thread.sleep(1000);
        System.out.println("method2");
      } catch (InterruptedException e) {}
    }
  }
}

class Producer extends Thread {
  static final int MAXQUEUE = 3;
  private Vector messages = new Vector();

  public void run() {
    try {
      while (true) {
        putMessage();
        sleep(1000);
      }
    }
    catch( InterruptedException e ) { }
  }

  private synchronized void putMessage()
    throws InterruptedException {
 
    while ( messages.size() == MAXQUEUE ) {
      System.out.println("waiting in Producer.putMessage() as messages queue full");
      wait();
    }
    messages.addElement( new java.util.Date().toString() );
    System.out.println("notifying threads waiting on producer");
    notify();
  }

  // Called by Consumer
  public synchronized String getMessage()
    throws InterruptedException {
    System.out.println("notifying producer to wakeup");
    notify();
    while ( messages.size() == 0 ) {
      System.out.println("waiting in Producer.getMessage() as messages queue empty");
      wait();
    }
    String message = (String)messages.firstElement();
    messages.removeElement( message );
    return message;
  }
}
 
class Consumer extends Thread {
  Producer producer;
 
  Consumer(Producer p) { producer = p;  }

  public void run() {
    try {
      for (int i =0; i < 20; ++i) {
        String message = producer.getMessage();
        System.out.println("Consumer.run(): Got message: " + message);
        sleep( 2000 );
      }
    }
    catch( InterruptedException e ) { }
  }

  public static void test() {
    Producer producer = new Producer();
    producer.start();
    new Consumer( producer ).start();
  }
}