public class ReaderWriter
{
	/**
 	* argv[0]: number of concurrently executed readers<br/>
 	* argv[1]: number of concurrently executed writers<br/>
 	* argv[2]: number of reads per executed reader<br/>
 	* argv[3]: number of reads per executed writer<br/>
	*/
	public static void main(String argv[])
{
		IntCRW data = new IntCRW();
		for (int i=0; i<Integer.parseInt(argv[0]); i++)
		{
			new Thread(new Writer(data, Integer.parseInt(argv[2]))).start();
		} //for
		for (int i=0; i<Integer.parseInt(argv[1]); i++)
		{
			new Thread(new Reader(data, Integer.parseInt(argv[3]))).start();
		} //for
	} //main()
} //class ReaderWriter
//---------------------------------------------------------
class IntCRW extends AccessControl
{
	int data;
	
	protected Object reallyRead()
	{
		return new Integer(data);
	} //reallyRead()
	
	protected void reallyWrite(Object obj)
	{
		data = ((Integer) obj).intValue();
	} //reallyWrite()
} //class IntCRW
//---------------------------------------------------------
class Reader implements Runnable
{
	private IntCRW data;
	private int noOfReads;

	public Reader(IntCRW data, int noOfReads)
	{
		this.data = data;
		this.noOfReads = noOfReads;
	} //constructor
	
	public void run()
	{
		for (int i=0; i<noOfReads; i++)
		{
			Integer myInt = (Integer) data.read();
			System.out.println(Thread.currentThread().getName()+" read value "+myInt );
		} //for
	} //run()
} //class Reader()
//---------------------------------------------------------
class Writer implements Runnable
{
	private IntCRW data;
	private int noOfWrites;

	public Writer(IntCRW data, int noOfWrites)
	{
		this.data = data;
		this.noOfWrites = noOfWrites;
	} //constructor
	
	public void run()
	{
		int value;
		for (int i=0; i<noOfWrites; i++)
		{
			value = (int) (Math.random()*Integer.MAX_VALUE);
			data.write(new Integer( value ));
			System.out.println(Thread.currentThread().getName()+" wrote value "+value );
		} //for
	} //run()
} //class Writer			
//---------------------------------------------------------
abstract class AccessControl
{
	private int activeReaders = 0;
	private int activeWriters = 0;
	private int waitingReaders = 0;
	private int waitingWriters = 0;
	
	protected abstract Object reallyRead();
	protected abstract void reallyWrite(Object obj);
	
	public Object read()
	{
		beforeRead();
		Object obj = reallyRead();
		afterRead();
		
		return obj;
	} //read()
	
	public void write(Object obj)
	{
		beforeWrite();
		reallyWrite(obj);
		afterWrite();
	} //write()
	
	private synchronized void beforeRead()
	{
		waitingReaders++;
		while( waitingWriters != 0 || activeWriters != 0 )
		{
			try
			{
				wait();
			} //try
			catch (InterruptedException ie)
			{
				System.out.println("An InterruptedException caught\n"+ie.getMessage());
				ie.printStackTrace();
				System.exit(1);
			} //catch()
		} //while
		waitingReaders--;
		activeReaders++;
	} //beforeRead()
	
	private synchronized void afterRead()
	{
		activeReaders--;
		notifyAll();
	} //afterRead
	
	private synchronized void beforeWrite()
	{
		waitingWriters++;
		while( activeReaders != 0 || activeWriters != 0 )
		{
			try
			{
				wait();
			} //try
			catch (InterruptedException ie)
			{
				System.out.println("An InterruptedException caught\n"+ie.getMessage());
				ie.printStackTrace();
				System.exit(1);
			} //catch()
		} //while
		waitingWriters--;
		activeWriters++;
	} //beforeWrite()
	
	private synchronized void afterWrite()
	{
		activeWriters--;
		notifyAll();
	} //afterWrite()
} //class AccessControl
