2
\$\begingroup\$

In this post, I attempted to compare the performance of two concurrency constructs:

  1. java.util.concurrent.Semaphore,
  2. ReentrantReadWriteLock.

The idea is to increment/read an int in a thread-safe manner.

net.coderodde.concurrent.fun.ConcurrentInt:

package net.coderodde.concurrent.fun;

import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * This interface defines the API for concurrent access to an {@code int} field.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Apr 10, 2019)
 */
public interface ConcurrentInt {

    /**
     * This interface method increments the integer.
     */
    public void increment();

    /**
     * This interface method reads the current value of the integer.
     * @return 
     */
    public int read();

    static void rethrowInterruptedException(InterruptedException ex,
                                            Logger logger) {
        String exceptionMessage = 
                getExceptionMessage(
                        Thread.currentThread().getName());

        logger.log(Level.SEVERE, exceptionMessage, ex);
        throw new RuntimeException(exceptionMessage, ex);
    }

    static String getExceptionMessage(String threadName) {
        return String.format("The thread '%s'"
                + " was interrupted.", threadName);
    }
}

net.coderodde.concurrent.fun.SemaphoreConcurrentInt:

package net.coderodde.concurrent.fun.impl;

import net.coderodde.concurrent.fun.ConcurrentInt;
import java.util.concurrent.Semaphore;
import java.util.logging.Logger;

/**
 * This class implements a {@link net.coderodde.concurrent.fun.ConcurrentInt} -
 * interface via (binary) semaphores.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Apr 10, 2019)
 */
public final class SemaphoreConcurrentInt implements ConcurrentInt {

    /**
     * This static field the object for neat printing of messages to a console.
     */
    private static final Logger LOGGER =
            Logger.getLogger(SemaphoreConcurrentInt.class.getName());

    /**
     * The actual integer field.
     */
    private volatile int integer;

    /**
     * A semaphore for concurrent access to the {@code integer}.
     */
    private final Semaphore mutex = new Semaphore(1, true);

    @Override
    public void increment() {
        try {
            mutex.acquire();
            integer++;
            mutex.release();
        } catch (InterruptedException ex) {
            ConcurrentInt.rethrowInterruptedException(ex, LOGGER);
        }
    }

    @Override
    public int read() {
        int value = 0; 

        try {
            mutex.acquire();
            value = integer;
            mutex.release();
        } catch (InterruptedException ex) {
            ConcurrentInt.rethrowInterruptedException(ex, LOGGER);
        }

        return value;
    }
}

net.coderodde.concurrent.fun.ReentrantLockConcurrentInt:

package net.coderodde.concurrent.fun.impl;

import java.util.concurrent.locks.ReentrantReadWriteLock;
import net.coderodde.concurrent.fun.ConcurrentInt;

/**
 * This class implements a {@link net.coderodde.concurrent.fun.ConcurrentInt} -
 * interface via a re-entrant lock.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Apr 10, 2019)
 */
public final class ReentrantLockConcurrentInt implements ConcurrentInt {

    /**
     * The actual integer field.
     */
    private volatile int integer;

    /**
     * A semaphore for concurrent access to the {@code integer}.
     */
    private final ReentrantReadWriteLock lock = 
            new ReentrantReadWriteLock(true);

    @Override
    public void increment() {
        lock.writeLock().lock();
        integer++;
        lock.writeLock().unlock();
    }

    @Override
    public int read() {
        lock.readLock().lock();
        int value = integer;
        lock.readLock().unlock();
        return value;
    }
}

Main.java:

package net.coderodde.concurrent.fun;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.coderodde.concurrent.fun.impl.ReentrantLockConcurrentInt;
import net.coderodde.concurrent.fun.impl.SemaphoreConcurrentInt;

/**
 * This class implements a demo program for benchmarking the concurrency 
 * facilities for incrementing an integer.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Apr 10, 2019)
 * @see net.coderodde.concurrent.fun.ConcurrentInt
 * @see net.coderodde.concurrent.fun.impl.SemaphoreConcurrentInt
 * @see net.coderodde.concurrent.fun.impl.
 */
public class Main {

    /**
     * This static inner class groups all the default values.
     */
    private static final class DefaultValues {

        /**
         * The default number oF read operations per thread.
         */
        private static final int DEFAULT_NUMBER_OF_READ_OPERATIONS = 1_000;

        /**
         * The default number of increment operations per thread.
         */
        private static final int DEFAULT_NUMBER_OF_INCREMENT_OPERATIONS = 1_000;

        /**
         * The default number of increment threads.
         */
        private static final int DEFAULT_NUMBER_OF_INCREMENT_THREADS = 10;

        /**
         * The default number of read threads.
         */
        private static final int DEFAULT_NUMBER_OF_READ_THREADS = 10;
    }

    /**
     * This static inner class groups all the minimum values.
     */
    private static final class MinimumValues {

        /**
         * The minimum number of read operations per thread.
         */
        private static final int MINIMUM_NUMBER_OF_READ_OPERATIONS = 1;

        /**
         * The minimum number of increment operations per thread.
         */
        private static final int MINIMUM_NUMBER_OF_INCREMENT_OEPRATIONS = 1;

        /**
         * The minimum number of increment threads.
         */
        private static final int MINIMUM_NUMBER_OF_INCREMENT_THREADS = 1;

        /**
         * The minimum number of read threads.
         */
        private static final int MINIMUM_NUMBER_OF_READ_THREADS = 1;
    }

    /**
     * This inner static class contains all the return values of this program.
     */
    private static final class ReturnValues {

        /**
         * Returned whenever we cannot parse the number of read threads.
         */
        private static final int ERROR_NUMBER_OF_READ_THREADS = 3;

        /**
         * Returned whenever we cannot parse the number of increment threads.
         */
        private static final int ERROR_NUMBER_OF_INCREMENT_THREADS = 2;

        /**
         * Returned whenever we cannot parse the number of read operations.
         */
        private static final int ERROR_NUMBER_OF_READ_OPERATIONS = 3;

        /**
         * Returned whenever we cannot parse the number of increment operations.
         */
        private static final int ERROR_NUMBER_OF_INCREMENT_OPERATIONS = 4;
    }

    /**
     * This static inner class contains all the command line flags.
     */
    private static final class CommandLineFlags {

        /**
         * Print verbose operations.
         */
        private static final String VERBOSE_SHORT = "-v";

        /**
         * Print verbose operation. Longer flag.
         */
        private static final String VERBOSE_LONG = "--verbose";

        /**
         * Print program version.
         */
        private static final String VERSION = "--version";

        /**
         * Print help message.
         */
        private static final String HELP_SHORT = "-h";

        /**
         * Print help message. Longer flag.
         */
        private static final String HELP_LONG = "--help";
    }

    private static final String HELP_MESSAGE = 
            "Usage:\n" + 
            "  java -jar This.jar [" + CommandLineFlags.VERSION + "] " +
            " [" + CommandLineFlags.HELP_SHORT + " | " + 
            CommandLineFlags.HELP_LONG + "] [" + 
            CommandLineFlags.VERBOSE_SHORT + " | " +
            CommandLineFlags.VERBOSE_LONG + "} [RT [IT [NR [NI]]]] where\n" +
            "  RT  number of read threads,\n" +
            "  IT  number of increment threads,\n" +
            "  NR  number of read operations,\n" +
            "  NI  number of increment threads.";

    private static final String VERSION_MESSAGE = 
            "ReadAccessTrial, v1.6 by Rodion \"rodde\" Efremov, Apr. 11, 2019"; 


    /**
     * The logger of this class.
     */
    private static final Logger LOGGER = Logger.getLogger(Main.class.getName());

    /**
     * This static inner class implements a thread type operating on a given
     * {@link net.coderodde.concurrent.fun.ConcurrentInt}.
     */
    private static final class ConcurrentIntIncrementerThread extends Thread {

        /**
         * The operation this thread performs.
         */
        enum Operation {

            /**
             * Denotes that this thread runs 
             * {@link net.coderodde.concurrent.fun.ConcurrentInt#increment()}.
             */
            INCREMENT,

            /**
             * Denotes that this thread runs
             * {@link net.coderodde.concurrent.fun.ConcurrentInt#read()}.
             */
            READ,
        }

        /**
         * The instance of {@link net.coderodde.concurrent.fun.ConcurrentInt} to
         * work on.
         */
        private final ConcurrentInt concurrentInt;

        /**
         * The number of operations to perform.
         */
        private final int numberOfOperations;

        /**
         * The operation to perform.
         */
        private final Operation operation;

        ConcurrentIntIncrementerThread(ConcurrentInt concurrentInt,
                                       int numberOfOperations,
                                       Operation operation) {
            this.concurrentInt = concurrentInt;
            this.numberOfOperations = numberOfOperations;
            this.operation = operation;
        }

        @Override
        public void run() {
            switch (operation) {
                case INCREMENT:
                    runIncrements();
                    break;

                case READ:
                    runReads();
                    break;

                default:
                    throw new IllegalStateException(
                            "Unknowon operation: " + operation);
            }
        }

        private void runIncrements() {
            for (int i = 0; i < numberOfOperations; i++) {
                concurrentInt.increment();
            }
        }

        private void runReads() {
            for (int i = 0; i < numberOfOperations; i++) {
                concurrentInt.increment();
            }
        }
    }

    private static int parseCommandLineArgument(String argument, 
                                                String numberFormatMessage,
                                                int exitValue) {
        try {
            return Integer.parseInt(argument);
        } catch (NumberFormatException ex) {
            LOGGER.log(Level.SEVERE,
                       String.format(numberFormatMessage, argument));
            System.exit(exitValue);
            return Integer.MIN_VALUE;
        }
    }

    private static final boolean  argumentsContainFlag(String[] arguments,
                                                       String flag) {
        for (int i = 0; i < arguments.length; i++) {
            if (flag.equals(arguments[i])) {
                return true;
            }
        }

        return false;
    }

    private static String[] removeArgument(String[] arguments, String flag) {
        int count = 0;

        for (String srgument : arguments) {
            if (srgument.equals(flag)) {
                count++;
            }
        }

        String[] newArguments = new String[arguments.length - count];

        for (int sourceIndex = 0, targetIndex = 0;
                sourceIndex < arguments.length;
                sourceIndex++) {
            String argument = arguments[sourceIndex];

            if (!argument.equals(flag)) {
                newArguments[targetIndex++] = argument;
            }
        }

        return newArguments;
    }

    public static void main(String[] args) {
        boolean verbose = false;

        if (argumentsContainFlag(args, CommandLineFlags.VERBOSE_SHORT)) {
            verbose = true;
            args = removeArgument(args, CommandLineFlags.VERBOSE_SHORT);
        }

        if (argumentsContainFlag(args, CommandLineFlags.VERBOSE_LONG)) {
            verbose = true;
            args = removeArgument(args, CommandLineFlags.VERBOSE_LONG);
        }

        if (argumentsContainFlag(args, CommandLineFlags.VERSION)) {
            args = removeArgument(args,CommandLineFlags.VERSION);
            System.out.println(VERSION_MESSAGE);
            System.exit(0);
        }

        if (argumentsContainFlag(args, CommandLineFlags.HELP_SHORT) ||
            argumentsContainFlag(args, CommandLineFlags.HELP_LONG)) {
            System.out.println(HELP_MESSAGE);
            System.exit(0);
        }

        int numberOfReadThreads =
                DefaultValues.DEFAULT_NUMBER_OF_READ_THREADS;

        int numberOfIncrementThreads = 
                DefaultValues.DEFAULT_NUMBER_OF_INCREMENT_THREADS;

        int numberOfReadOperations = 
                DefaultValues.DEFAULT_NUMBER_OF_READ_OPERATIONS;

        int numberOfIncrementOperations = 
                DefaultValues.DEFAULT_NUMBER_OF_INCREMENT_OPERATIONS;

        switch (args.length) {
            case 4:
            // Fall through!
            numberOfIncrementOperations =
                    parseCommandLineArgument(
                            args[3], 
                            "Could not parse '%s' as the number of increment " + 
                                "operations.",
                            ReturnValues.ERROR_NUMBER_OF_INCREMENT_OPERATIONS);
            // Fall through!
            case 3:
                numberOfReadOperations = 
                    parseCommandLineArgument(
                            args[2],
                            "Could not parse'%s' as the number of read " +
                                "operations.",
                            ReturnValues.ERROR_NUMBER_OF_READ_OPERATIONS);
            // Fall through!
            case 2:
                numberOfIncrementThreads = 
                    parseCommandLineArgument(
                            args[1],
                            "Could not parse '%s' as the number of increment " +
                                "threads.",
                            ReturnValues.ERROR_NUMBER_OF_INCREMENT_THREADS);
            // Fall through!
            case 1:
                numberOfReadThreads = 
                    parseCommandLineArgument(
                            args[0],
                            "Coudl not parse '%s' as the number of read " + 
                                "threads.",
                            ReturnValues.ERROR_NUMBER_OF_READ_THREADS);
            }

        int effectiveNumberOfReadThreads = 
                Math.max(numberOfReadThreads,
                         MinimumValues.MINIMUM_NUMBER_OF_READ_THREADS);

        int effecitveNumberOfIncrementThreads = 
                Math.max(numberOfIncrementThreads,
                         MinimumValues.MINIMUM_NUMBER_OF_INCREMENT_THREADS);

        int effectiveNumberOfReadOperations = 
                Math.max(numberOfReadOperations,
                         MinimumValues.MINIMUM_NUMBER_OF_READ_OPERATIONS);

        int effectiveNumberOfIncrementOperations = 
                Math.max(numberOfIncrementOperations,
                         MinimumValues.MINIMUM_NUMBER_OF_INCREMENT_OEPRATIONS);

        if (verbose) {
            printEffectiveArguments(numberOfReadThreads,
                                    numberOfIncrementThreads,
                                    numberOfReadOperations,
                                    numberOfIncrementOperations,
                                    effectiveNumberOfReadThreads,
                                    effecitveNumberOfIncrementThreads,
                                    effectiveNumberOfReadOperations,
                                    effectiveNumberOfIncrementOperations);
        }

        ConcurrentInt concurrentInt1 = new SemaphoreConcurrentInt();
        ConcurrentInt concurrentInt2 = new ReentrantLockConcurrentInt();
        long duration;

        duration = benchmark(concurrentInt1,
                             numberOfReadOperations,
                             numberOfIncrementOperations,
                             numberOfReadThreads,
                             numberOfIncrementThreads);

        System.out.println(String.format("%d in %d milliseconds.", 
                                         concurrentInt1.read(),
                                         duration));

        duration = benchmark(concurrentInt2,
                             numberOfReadThreads,
                             numberOfIncrementThreads,
                             numberOfReadOperations,
                             numberOfIncrementOperations);

        System.out.println(String.format("%d in %d milliseconds.", 
                                         concurrentInt2.read(),
                                         duration));
    }

     private static void printEffectiveArguments(
             int numberOfReadThreads,
             int numberOfIncrementThreads,
             int numberOfReadOperations,
             int numberOfIncrementOperations,
             int effectiveNumberOfReadThreads,
             int effectiveNumberOfIncrementThreads,
             int effectiveNumberOfReadOperations,
             int effectiveNumberOfIncrementOperations) {
         System.out.println("Requested parameters >>>");
         System.out.println("Number of read threads: " + numberOfReadThreads);
         System.out.println("Number of increment threads: " + 
                            numberOfIncrementThreads);
         System.out.println("Number of read operations: " +
                            numberOfReadOperations);
         System.out.println("Number of increment operations: " + 
                            numberOfIncrementOperations);
         System.out.println("Effective parameters >>>");
         System.out.println("Effective number of read threads: " + 
                            effectiveNumberOfReadThreads);
         System.out.println("Effective number of increment threads: " + 
                            effectiveNumberOfIncrementThreads);
         System.out.println("Effective number of read operations: " +
                            effectiveNumberOfReadOperations);
         System.out.println("Effective number of increment operations: " + 
                            effectiveNumberOfIncrementOperations);
     }

    /**
     * This static method performs the actual benchmaark.
     * @param concurrentInt               the {@link net.coderodde.concurrent.fun.ConcurrentInt}
     *                                    to operate on.
     * @param numberOfReadThreads         the number of read threads.
     * @param numberOfIncrementThreads    the number of increment threads.
     * @param numberOfReadOperations      the number of read operations per 
     *                                    thread.
     * @param numberOfIncrementOperations the number of increment operations per
     *                                    thread.
     * @return the number of milliseconds that took to operate on the 
     *         {@link net.coderodde.concurrent.fun.ConcurrentInt}.
     */
    private static final long benchmark(ConcurrentInt concurrentInt,
                                        int numberOfReadThreads,
                                        int numberOfIncrementThreads,
                                        int numberOfReadOperations,
                                        int numberOfIncrementOperations) {

        List<ConcurrentIntIncrementerThread> 
                concurrenetIntIncrementerThreadList =
                new ArrayList<>(numberOfReadThreads + 
                                numberOfIncrementThreads);

        for (int i = 0; i < numberOfReadThreads; i++) {
            concurrenetIntIncrementerThreadList.add(
                    new ConcurrentIntIncrementerThread(
                            concurrentInt,
                            numberOfReadOperations,
                            Main.ConcurrentIntIncrementerThread
                                .Operation
                                .READ));
        }

        for (int i = 0; i < numberOfIncrementThreads; i++) {
            concurrenetIntIncrementerThreadList.add(
                    new ConcurrentIntIncrementerThread(
                            concurrentInt,
                            numberOfIncrementOperations,
                            Main.ConcurrentIntIncrementerThread
                                    .Operation
                                    .INCREMENT));
        }

        Collections.shuffle(concurrenetIntIncrementerThreadList);
        long startMoment = System.currentTimeMillis();

        for (ConcurrentIntIncrementerThread thread : concurrenetIntIncrementerThreadList) {
            thread.start();
        }

        for (ConcurrentIntIncrementerThread thread : concurrenetIntIncrementerThreadList) {
            try {
                thread.join();
            } catch (InterruptedException ex) {
                LOGGER.log(Level.SEVERE,
                           String.format(
                                   "The thread '{0}' threw " + 
                                           "InterruptedException: {1}", 
                                   thread.getName(),
                                   ex.getMessage()));

                throw new RuntimeException(ex);
            }
        }

        long endMoment = System.currentTimeMillis();
        return endMoment - startMoment;
    }
}

Sample output

java -jar ReadAccessTrial.jar 2 2 2000 2000
8000 in 803 milliseconds.
8000 in 157 milliseconds.

Critique request

I would like to hear comments on coding conventions, readability and maintainability, to name a few.

\$\endgroup\$
  • 1
    \$\begingroup\$ Request for Clarification: You're question lacks, well, a question. What type of feedback are you interested in? Coding format? Performance? Readability/Maintainability? Better ways to do the same process/ tasks? \$\endgroup\$ – DapperDan Apr 11 at 16:31
  • \$\begingroup\$ @DapperDan Addes a Critique request section to my post. \$\endgroup\$ – coderodde Apr 12 at 5:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.