/*
 * Written by Doug Lea with assistance from members of JCP JSR-166
 * Expert Group and released to the public domain, as explained at
 * http://creativecommons.org/publicdomain/zero/1.0/
 */

// Yet another contended object monitor throughput test
// adapted from bug reports

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

class Producer extends Thread
{
    //  private static Hashtable buddiesOnline = new Hashtable();
    private static Map buddiesOnline = new ConcurrentHashMap();
    public Producer(String name) { super(name); }

    public void run() {
        Object key = null;
        final ReentrantLock dr = RLJBar.DeathRow;
        final ReentrantLock bar = RLJBar.bar;
        final ReentrantLock end = RLJBar.End;
        final Condition endCondition = RLJBar.EndCondition;
        if (RLJBar.OneKey) key = new Integer(0);         // per-thread v. per iteration

        // The barrier has a number of interesting effects:
        // 1.     It enforces full LWP provisioning on T1.
        //                (nearly all workers park concurrently).
        // 2.     It gives the C2 compiler thread(s) a chance to run.
        //                By transiently quiescing the workings the C2 threads
        //                might avoid starvation.
        //

        try {
            bar.lock();
            try {
                ++RLJBar.nUp;
                if (RLJBar.nUp == RLJBar.nThreads) {
                    if (RLJBar.quiesce != 0) {
                        RLJBar.barCondition.awaitNanos(RLJBar.quiesce * 1000000);
                    }
                    RLJBar.epoch = System.currentTimeMillis();
                    RLJBar.barCondition.signalAll();
                    //                  System.out.print ("Consensus ");
                }
                if (RLJBar.UseBar) {
                    while (RLJBar.nUp != RLJBar.nThreads) {
                        RLJBar.barCondition.await();
                    }
                }
            }
            finally {
                bar.unlock();
            }
        } catch (Exception ex) {
            System.out.println("Exception in barrier: " + ex);
        }

        // Main execution time ... the code being timed ...
        // HashTable.get() is highly contended (serial).
        for (int loop = 1; loop < 100000 ;loop++) {
            if (!RLJBar.OneKey) key = new Integer(0);
            buddiesOnline.get(key);
        }

        // Mutator epilog:
        // The following code determines if the test will/wont include (measure)
        // thread death time.

        end.lock();
        try {
            ++RLJBar.nDead;
            if (RLJBar.nDead == RLJBar.nUp) {
                //          System.out.print((System.currentTimeMillis()-RLJBar.epoch) + " ms");
                endCondition.signalAll();
            }
        }
        finally {
            end.unlock();
        }
        dr.lock();
        dr.unlock();
    }
}


public class RLJBar                             // ProdConsTest
{

    public static final int ITERS = 10;
    public static boolean OneKey = false;                        // alloc once or once per iteration

    public static boolean UseBar = false;
    public static int nThreads = 100;
    public static int nUp = 0;
    public static int nDead = 0;
    public static ReentrantLock bar = new ReentrantLock();
    public static Condition barCondition = bar.newCondition();
    public static long epoch;
    public static ReentrantLock DeathRow = new ReentrantLock();
    public static ReentrantLock End = new ReentrantLock();
    public static int quiesce = 0;
    public static Condition EndCondition = End.newCondition();

    public static void main(String[] args)    {
        int argix = 0;
        if (argix < args.length && args[argix].equals("-o")) {
            ++argix;
            OneKey = true;
            System.out.println("OneKey");
        }
        if (argix < args.length && args[argix].equals ("-b")) {
            ++argix;
            UseBar = true;
            System.out.println("UseBar");
        }
        if (argix < args.length && args[argix].equals ("-q")) {
            ++argix;
            if (argix < args.length) {
                quiesce = Integer.parseInt(args[argix++]);
                System.out.println("Quiesce " + quiesce + " msecs");
            }
        }
        for (int k = 0; k < ITERS; ++k)
            oneRun();
    }

    public static void oneRun() {
        DeathRow = new ReentrantLock();
        End = new ReentrantLock();
        EndCondition = End.newCondition();

        nDead = nUp = 0;
        long cyBase = System.currentTimeMillis();
        DeathRow.lock();
        try {
            for (int i = 1; i <= nThreads; i++) {
                new Producer("Producer" + i).start();
            }
            try {
                End.lock();
                try {
                    while (nDead != nThreads)
                        EndCondition.await();
                }
                finally {
                    End.unlock();
                }
            } catch (Exception ex) {
                System.out.println("Exception in End: " + ex);
            }
        }
        finally {
            DeathRow.unlock();
        }
        System.out.println("Outer time: " + (System.currentTimeMillis()-cyBase));

        // Let workers quiesce/exit.
        try { Thread.sleep (1000); } catch (Exception ex) {};
    }
}
