//  Readers/Writers with concurrent read or exclusive write
//
//   Program adapted from Greg Andrews' textbook website
//   http://www.cs.arizona.edu/people/greg/mpdbook/programs/
//   
//   Compile:   javac ReadWrite.java
//   Run:       java  ReadWrite rounds

import java.util.Random;

class Database {     
  private Random generator = new Random();
  private int data = 0;  // the "database"
  int nr = 0;
  private synchronized void startRead() {
    nr++;
  }
  private synchronized void endRead() {
    nr--;
    if (nr==0) notify();  // awaken waiting Writers
  }
  public void read() {
    startRead();
    System.out.println("read:  " + data);
    endRead();
  }
  public synchronized void write() {
    int temp;
    while (nr>0)
      try { wait(); } 
        catch (InterruptedException ex) {return;}

    temp = data;
    data = 99999;  // to simulate an inconsistent temporary state
    try {
        Thread.sleep(generator.nextInt(500));   // wait a bit
    } catch (java.lang.InterruptedException e) {}
    data = temp+1;     // back to a safe state
    System.out.println("wrote:  " + data);
    notify();    // awaken another waiting Writer
  }
}
  
class Reader extends Thread {
  int rounds;
  Database RW;
  private Random generator = new Random();

  public Reader(int rounds, Database RW) {
    this.rounds = rounds;
    this.RW = RW;
  }
  public void run() {
    for (int i = 0; i<rounds; i++) {
	try {
   	  Thread.sleep(generator.nextInt(500));
        } catch (java.lang.InterruptedException e) {}
        RW.read();
    }
  }
}

class Writer extends Thread {
  int rounds;
  Database RW;
  private Random generator = new Random();

  public Writer(int rounds, Database RW) {
    this.rounds = rounds;
    this.RW = RW;
  }
  public void run() {
    for (int i = 0; i<rounds; i++) {
	try {
   	  Thread.sleep(generator.nextInt(500));
        } catch (java.lang.InterruptedException e) {}
        RW.write();
    }
  }
}

class ReadWrite {  // driver program -- two readers and two writers
  static Database RW = new Database();
  public static void main(String[] arg) {
    int rounds = Integer.parseInt(arg[0],10);
    new Reader(rounds, RW).start();
    new Reader(rounds, RW).start();
    new Writer(rounds, RW).start();
    new Writer(rounds, RW).start();
  }
}
