public class MultithreadMean  {

    private static final String IDENT_PATTERN = 
            "Multithreaded computation example: %s\n";
    private static final String INIT_DATA_PATTERN = "Initializing data ";
    private static final String INIT_THREADS_PATTERN = 
            "Initializing %d threads ";
    private static final String TIME_PATTERN = "(%d ms)\n";
    private static final String PROCESS_PATTERN = "Processing data\n";
    private static final String RESULT_PATTERN = 
            "\tCount = %d\n\tArithmetic mean = %f\n\tGeometric mean = %f\n";
    private static final int ARRAY_SIZE = 10000000;
    private static final int RANDOM_SEED = 20110523;
    private static final int NUM_THREADS = 4;
    
    private byte[] values;
    private int count;
    private long sum;
    private double logSum;
    private Worker[] workers;
    
    public static void main(String[] args) {
        MultithreadMean manager = new MultithreadMean();
        System.out.printf(IDENT_PATTERN, "Race Condition");
        manager.initializeData();
        manager.initializeThreads();
        manager.process();
    }

    private void initializeData() {
        long start = System.currentTimeMillis();
        java.util.Random rng = new java.util.Random(RANDOM_SEED);
        System.out.printf(INIT_DATA_PATTERN);
        values = new byte[ARRAY_SIZE];
        for (int i = 0; i < values.length; i++) {
            values[i] = (byte) (1 + rng.nextInt(Byte.MAX_VALUE));
        }
        count = 0;
        sum = 0;
        logSum = 0;
        System.out.printf(TIME_PATTERN, System.currentTimeMillis() - start);
    }
    
    private void process() {
        long start = System.currentTimeMillis();
        System.out.printf(PROCESS_PATTERN);
        processWithThreads();
        System.out.printf(RESULT_PATTERN, 
                count, sum / (double) count, Math.exp(logSum / count));
        System.out.printf(TIME_PATTERN, System.currentTimeMillis() - start);
    }
    
    private void initializeThreads() {
        long start = System.currentTimeMillis();
        int sliceLength = values.length / NUM_THREADS;
        System.out.printf(INIT_THREADS_PATTERN, NUM_THREADS);
        workers = new Worker[NUM_THREADS];
        for (int i = 0; i < workers.length; i++) {
            workers[i] = new Worker(sliceLength * i, sliceLength);
        }
        System.out.printf(TIME_PATTERN, System.currentTimeMillis() - start);
    }
    
    private void processWithThreads() {
        for (int i = 0; i < workers.length; i++) {
            workers[i].start();
        }
        for (int i = 0; i < workers.length; i++) {
            try {
                workers[i].join();
            }
            catch (InterruptedException e) {}
        }
    }
    
    public void update(byte value) {
        sum += value;
        logSum += Math.log(value);
        count++;
    }
    
    private class Worker extends Thread {

        private int rangeStart;
        private int rangeLength;

        public Worker(int rangeStart, int rangeLength) {
            this.rangeStart = rangeStart;
            this.rangeLength = rangeLength;
        }

        public void run() {
            for (int i = rangeStart; i < rangeStart + rangeLength; i++) {
                update(values[i]);
            }
        }

    }

}
