public class CounterTestV3 {

    public class Counter {
        /** A long will be used to store the counter value */
        private long counter;

        /**
         * Create and initialize the counter value to 0
         */
        public Counter() {
            this.counter = 0;
        }
        /**
         * Increment the counter value by 1
         */
        public synchronized void increment() {
            this.counter += 1;
        }
        /**
         * @return the counter value
         */
        public long get() {
            return this.counter;
        }
    }

    public class IncrementorRunnable implements Runnable {
        /** The counter we want to increment */
        private Counter counter;
        /** The number of times the counter is going to be incremented */
        private long nbIterations;

        /**
         * Constructor
         *
         * @param counter: The counter we want to increment
         * @param nbIterations: The number of times the counter is going to be incremented
         */
        public IncrementorRunnable(Counter counter, long nbIterations) {
            this.counter = counter;
            this.nbIterations = nbIterations;
        }

        /**
         * Implementation of what the thread will be doing once spawned.
         *
         * While the iterations number is positive:
         *   - increment the counter
         *   - decrement the iterations counter
         */
        @Override
        public void run() {
            while (this.nbIterations > 0) {
                this.counter.increment();
                this.nbIterations -= 1;
            }
        }
    }

    private Counter counter;

    private void runThreads(int numThreads, int numIterations) {

        this.counter = new Counter();

        /** Create all threads **/
        Thread threads[] = new Thread[numThreads];
        for (int i=0; i < numThreads; i++) {
            threads[i] = new Thread(new IncrementorRunnable(counter, numIterations));
        }
        /** Start all threads **/
        for (int i=0; i < numThreads; i++) {
            threads[i].start();
        }
        /** Wait for all threads **/
        for (int i=0; i < numThreads; i++) {
            try {
                threads[i].join();
            } catch (InterruptedException e) {}
        }
        /** Print actual and expected counter values **/
        long expected = numThreads * numIterations;
        if (this.counter.get() == expected) {
            System.out.println("Final counter value: " + this.counter.get() +
                    " (Expected = " + expected + ")");
        } else {
            System.out.println("Final counter value: " + this.counter.get() +
                    " \033[1;31m(Expected = " + expected + "!!)\033[0m");
        }

    }

    public CounterTestV3(int numThreads, int numIterations) {
        runThreads(numThreads, numIterations);
    }

    /**
     * Test function
     *
     * @param args: command-line arguments
     */
    public static void main(String[] args) {

        /** Parse command-line arguments **/
        if (args.length != 2) {
            System.err.println("Usage: java CounterTestV1 <num threads> <num iterations>");
            System.exit(1);
        }

        try {
            new CounterTestV3(Integer.parseInt(args[0]), Integer.parseInt(args[1]));
        } catch (NumberFormatException e) {
            System.err.println("Invalid command-line arguments");
            System.exit(1);
        }

    }
}
