Java 初始化ConcurrentHashMap值的最快方法

Java 初始化ConcurrentHashMap值的最快方法,java,multithreading,concurrency,concurrenthashmap,Java,Multithreading,Concurrency,Concurrenthashmap,ConcurrentHashMap通常在并发环境中用于在键下聚合某些事件,就像计算某些字符串值的点击次数一样。如果我们事先不知道密钥,我们需要有一种根据需要初始化密钥的好方法,它应该在并发性方面是快速和安全的。 对于这个问题,在效率方面最好的模式是什么 我将使用声明如下的模型映射: ConcurrentHashMap<String, AtomicInteger> map = new ConcurrentHashMap<>(); 第二个使用ConcurrentHashMa

ConcurrentHashMap通常在并发环境中用于在键下聚合某些事件,就像计算某些字符串值的点击次数一样。如果我们事先不知道密钥,我们需要有一种根据需要初始化密钥的好方法,它应该在并发性方面是快速和安全的。 对于这个问题,在效率方面最好的模式是什么

我将使用声明如下的模型映射:

ConcurrentHashMap<String, AtomicInteger> map = new ConcurrentHashMap<>();
第二个使用ConcurrentHashMap.ComputeFabSent:


哪一个更适合这个任务?还有其他方法吗?

不幸的是,直到jdk1.8.0131,ComputeFabSent始终进入同步块,重新考虑密钥是否已经存在,这使得它比putIfAbsent慢得多

该基准测试证实了这一点,根据竞争级别,putIfAbsent的速度似乎是ComputeFabSent的2到50倍

我们可以使用putIfAbsent方法来创建更快的ComputeFabSent。 唯一的区别是,如果同时初始化同一个密钥,这个新的ComputeFabSent可以多次调用初始化函数。基准测试结果与“putIfAbsent”相同,因为它是相同的代码,这并不令人惊讶,但如果有人想测试它,这里是基准测试:

import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;

public class CocnurrentHashMap2Benchmark {
    private final static int numberOfRuns = 1000000;
    private final static int numberOfThreads = Runtime.getRuntime().availableProcessors();
    private final static int keysSize = 10;
    private final static String[] strings = new String[keysSize];
    static {
        for (int n = 0; n < keysSize; n++) {
            strings[n] = "" + (char) ('A' + n);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int n = 0; n < 20; n++) {
            testPutIfAbsent();
            testComputeIfAbsent2Lambda();
        }
    }

    private static void testPutIfAbsent() throws InterruptedException {
        final AtomicLong totalTime = new AtomicLong();
        final ConcurrentHashMap<String, AtomicInteger> map = new ConcurrentHashMap<String, AtomicInteger>();
        final Random random = new Random();
        ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);

        for (int i = 0; i < numberOfThreads; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    long start, end;
                    for (int n = 0; n < numberOfRuns; n++) {
                        String s = strings[random.nextInt(strings.length)];
                        start = System.nanoTime();

                        AtomicInteger count = map.get(s);
                        if (count == null) {
                            count = new AtomicInteger(0);
                            AtomicInteger prevCount = map.putIfAbsent(s, count);
                            if (prevCount != null) {
                                count = prevCount;
                            }
                        }
                        count.incrementAndGet();
                        end = System.nanoTime();
                        totalTime.addAndGet(end - start);
                    }
                }
            });
        }
        executorService.shutdown();
        executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
        System.out.println("Test " + Thread.currentThread().getStackTrace()[1].getMethodName()
                + " average time per run: " + (double) totalTime.get() / numberOfThreads / numberOfRuns + " ns");
    }


     private static void testComputeIfAbsent2Lambda() throws InterruptedException {
            final AtomicLong totalTime = new AtomicLong();
            final ConcurrentHashMap2<String, AtomicInteger> map = new ConcurrentHashMap2<String, AtomicInteger>();
            final Random random = new Random();
            ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);
            for (int i = 0; i < numberOfThreads; i++) {
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        long start, end;
                        for (int n = 0; n < numberOfRuns; n++) {
                            String s = strings[random.nextInt(strings.length)];
                            start = System.nanoTime();

                            AtomicInteger count = map.computeIfAbsent2(s, (k) -> new AtomicInteger(0));
                            count.incrementAndGet();

                            end = System.nanoTime();
                            totalTime.addAndGet(end - start);
                        }
                    }
                });
            }
            executorService.shutdown();
            executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
            System.out.println("Test " + Thread.currentThread().getStackTrace()[1].getMethodName()
                    + " average time per run: " + (double) totalTime.get() / numberOfThreads / numberOfRuns + " ns");
        }

        public static class ConcurrentHashMap2<K,V> extends ConcurrentHashMap<K,V> {

            /**
             * If there is no mapping for the key then computes and puts the mapping,
             * otherwise it simply return the value for that key.
             * In case of concurrent initialization of the same key the mappingFunction can be called more than once.
             * @param key - the key to be initialized or retrieved
             * @param mappingFunction - the function to be called for computation of initial value.
             * @return computed value if the key wasn't already in the map otherwise return the actual value for provided key.
             */
            public V computeIfAbsent2(K key, Function<K,V> mappingFunction) {
                V value = get(key);
                if (value == null) {
                    value = mappingFunction.apply(key);
                    V prevValue = putIfAbsent(key, value);
                    if (prevValue != null) {
                        value = prevValue;
                    }
                }
                return value;
            }
        }
}
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;

public class CocnurrentHashMap2PutBenchmark {
    private final static int numberOfRuns = 1000000;
    private final static int numberOfThreads = Runtime.getRuntime().availableProcessors();

    public static void main(String[] args) throws InterruptedException {
        for (int n = 0; n < 20; n++) {
            testComputeIfAbsent2();
            testComputeIfAbsent();
        }
    }

    private static void testComputeIfAbsent2() throws InterruptedException {
        final AtomicLong totalTime = new AtomicLong();
        final ConcurrentHashMap2<Integer, String> map = new ConcurrentHashMap2<Integer, String>();
        ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);
        for (int i = 0; i < numberOfThreads; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    long start, end;
                    for (int n = 0; n < numberOfRuns; n++) {
                        Integer key = Integer.valueOf(n);
                        start = System.nanoTime();

                        String value = map.computeIfAbsent2(key, (k) -> "value");

                        end = System.nanoTime();
                        totalTime.addAndGet(end - start);
                    }
                }
            });
        }
        executorService.shutdown();
        executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
        System.out.println("Test " + Thread.currentThread().getStackTrace()[1].getMethodName()
                + " average time per run: " + (double) totalTime.get() / numberOfThreads / numberOfRuns + " ns");
    }

    private static void testComputeIfAbsent() throws InterruptedException {
        final AtomicLong totalTime = new AtomicLong();
        final ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<Integer, String>();
        ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);
        for (int i = 0; i < numberOfThreads; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    long start, end;
                    for (int n = 0; n < numberOfRuns; n++) {
                        Integer key = Integer.valueOf(n);
                        start = System.nanoTime();

                        String value = map.computeIfAbsent(key, (k) -> "value");

                        end = System.nanoTime();
                        totalTime.addAndGet(end - start);
                    }
                }
            });
        }
        executorService.shutdown();
        executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
        System.out.println("Test " + Thread.currentThread().getStackTrace()[1].getMethodName()
                + " average time per run: " + (double) totalTime.get() / numberOfThreads / numberOfRuns + " ns");
    }

    public static class ConcurrentHashMap2<K, V> extends ConcurrentHashMap<K, V> {

        /**
         * If there is no mapping for the key then computes and puts the
         * mapping, otherwise it simply return the value for that key. In case
         * of concurrent initialization of the same key the mappingFunction can
         * be called more than once.
         * 
         * @param key
         *            - the key to be initialized or retrieved
         * @param mappingFunction
         *            - the function to be called for computation of initial
         *            value.
         * @return computed value if the key wasn't already in the map otherwise
         *         return the actual value for provided key.
         */
        public V computeIfAbsent2(K key, Function<K, V> mappingFunction) {
            V value = get(key);
            if (value == null) {
                value = mappingFunction.apply(key);
                V prevValue = putIfAbsent(key, value);
                if (prevValue != null) {
                    value = prevValue;
                }
            }
            return value;
        }
    }
}
如果我们比较键不存在时将映射放入映射的速度,那么新的“ComputeFabsent2”似乎也快得多。基准:

import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;

public class CocnurrentHashMap2Benchmark {
    private final static int numberOfRuns = 1000000;
    private final static int numberOfThreads = Runtime.getRuntime().availableProcessors();
    private final static int keysSize = 10;
    private final static String[] strings = new String[keysSize];
    static {
        for (int n = 0; n < keysSize; n++) {
            strings[n] = "" + (char) ('A' + n);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int n = 0; n < 20; n++) {
            testPutIfAbsent();
            testComputeIfAbsent2Lambda();
        }
    }

    private static void testPutIfAbsent() throws InterruptedException {
        final AtomicLong totalTime = new AtomicLong();
        final ConcurrentHashMap<String, AtomicInteger> map = new ConcurrentHashMap<String, AtomicInteger>();
        final Random random = new Random();
        ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);

        for (int i = 0; i < numberOfThreads; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    long start, end;
                    for (int n = 0; n < numberOfRuns; n++) {
                        String s = strings[random.nextInt(strings.length)];
                        start = System.nanoTime();

                        AtomicInteger count = map.get(s);
                        if (count == null) {
                            count = new AtomicInteger(0);
                            AtomicInteger prevCount = map.putIfAbsent(s, count);
                            if (prevCount != null) {
                                count = prevCount;
                            }
                        }
                        count.incrementAndGet();
                        end = System.nanoTime();
                        totalTime.addAndGet(end - start);
                    }
                }
            });
        }
        executorService.shutdown();
        executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
        System.out.println("Test " + Thread.currentThread().getStackTrace()[1].getMethodName()
                + " average time per run: " + (double) totalTime.get() / numberOfThreads / numberOfRuns + " ns");
    }


     private static void testComputeIfAbsent2Lambda() throws InterruptedException {
            final AtomicLong totalTime = new AtomicLong();
            final ConcurrentHashMap2<String, AtomicInteger> map = new ConcurrentHashMap2<String, AtomicInteger>();
            final Random random = new Random();
            ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);
            for (int i = 0; i < numberOfThreads; i++) {
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        long start, end;
                        for (int n = 0; n < numberOfRuns; n++) {
                            String s = strings[random.nextInt(strings.length)];
                            start = System.nanoTime();

                            AtomicInteger count = map.computeIfAbsent2(s, (k) -> new AtomicInteger(0));
                            count.incrementAndGet();

                            end = System.nanoTime();
                            totalTime.addAndGet(end - start);
                        }
                    }
                });
            }
            executorService.shutdown();
            executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
            System.out.println("Test " + Thread.currentThread().getStackTrace()[1].getMethodName()
                    + " average time per run: " + (double) totalTime.get() / numberOfThreads / numberOfRuns + " ns");
        }

        public static class ConcurrentHashMap2<K,V> extends ConcurrentHashMap<K,V> {

            /**
             * If there is no mapping for the key then computes and puts the mapping,
             * otherwise it simply return the value for that key.
             * In case of concurrent initialization of the same key the mappingFunction can be called more than once.
             * @param key - the key to be initialized or retrieved
             * @param mappingFunction - the function to be called for computation of initial value.
             * @return computed value if the key wasn't already in the map otherwise return the actual value for provided key.
             */
            public V computeIfAbsent2(K key, Function<K,V> mappingFunction) {
                V value = get(key);
                if (value == null) {
                    value = mappingFunction.apply(key);
                    V prevValue = putIfAbsent(key, value);
                    if (prevValue != null) {
                        value = prevValue;
                    }
                }
                return value;
            }
        }
}
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;

public class CocnurrentHashMap2PutBenchmark {
    private final static int numberOfRuns = 1000000;
    private final static int numberOfThreads = Runtime.getRuntime().availableProcessors();

    public static void main(String[] args) throws InterruptedException {
        for (int n = 0; n < 20; n++) {
            testComputeIfAbsent2();
            testComputeIfAbsent();
        }
    }

    private static void testComputeIfAbsent2() throws InterruptedException {
        final AtomicLong totalTime = new AtomicLong();
        final ConcurrentHashMap2<Integer, String> map = new ConcurrentHashMap2<Integer, String>();
        ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);
        for (int i = 0; i < numberOfThreads; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    long start, end;
                    for (int n = 0; n < numberOfRuns; n++) {
                        Integer key = Integer.valueOf(n);
                        start = System.nanoTime();

                        String value = map.computeIfAbsent2(key, (k) -> "value");

                        end = System.nanoTime();
                        totalTime.addAndGet(end - start);
                    }
                }
            });
        }
        executorService.shutdown();
        executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
        System.out.println("Test " + Thread.currentThread().getStackTrace()[1].getMethodName()
                + " average time per run: " + (double) totalTime.get() / numberOfThreads / numberOfRuns + " ns");
    }

    private static void testComputeIfAbsent() throws InterruptedException {
        final AtomicLong totalTime = new AtomicLong();
        final ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<Integer, String>();
        ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);
        for (int i = 0; i < numberOfThreads; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    long start, end;
                    for (int n = 0; n < numberOfRuns; n++) {
                        Integer key = Integer.valueOf(n);
                        start = System.nanoTime();

                        String value = map.computeIfAbsent(key, (k) -> "value");

                        end = System.nanoTime();
                        totalTime.addAndGet(end - start);
                    }
                }
            });
        }
        executorService.shutdown();
        executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
        System.out.println("Test " + Thread.currentThread().getStackTrace()[1].getMethodName()
                + " average time per run: " + (double) totalTime.get() / numberOfThreads / numberOfRuns + " ns");
    }

    public static class ConcurrentHashMap2<K, V> extends ConcurrentHashMap<K, V> {

        /**
         * If there is no mapping for the key then computes and puts the
         * mapping, otherwise it simply return the value for that key. In case
         * of concurrent initialization of the same key the mappingFunction can
         * be called more than once.
         * 
         * @param key
         *            - the key to be initialized or retrieved
         * @param mappingFunction
         *            - the function to be called for computation of initial
         *            value.
         * @return computed value if the key wasn't already in the map otherwise
         *         return the actual value for provided key.
         */
        public V computeIfAbsent2(K key, Function<K, V> mappingFunction) {
            V value = get(key);
            if (value == null) {
                value = mappingFunction.apply(key);
                V prevValue = putIfAbsent(key, value);
                if (prevValue != null) {
                    value = prevValue;
                }
            }
            return value;
        }
    }
}

由于ComputeFabSent和putIfAbsent被用于两种不同的事情,所以如此天真地比较它们是没有意义的。当您想要以原子方式计算一个可以基于键的值时,可以使用ComputeFabSent。当您希望在映射中原子地放置一个简单值时,可以使用putIfAbsent。简单的操作速度更快也就不足为奇了。@Kayaman不太奇怪,为什么ComputeFabSent不只是返回该键的当前值,而该键已经存在?为什么在密钥已经存在的情况下它会进入synchronized block?我最近没有看过代码,但最有可能的是确保正确的发生在关系之前。该方法需要是原子的和线程安全的。你是说有不必要的同步发生?这将是一个很好的发现。@Kayaman是的,这正是我要说的。好吧,看起来我们都是对的,但在Java 9中它将被改变。
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;

public class CocnurrentHashMap2Benchmark {
    private final static int numberOfRuns = 1000000;
    private final static int numberOfThreads = Runtime.getRuntime().availableProcessors();
    private final static int keysSize = 10;
    private final static String[] strings = new String[keysSize];
    static {
        for (int n = 0; n < keysSize; n++) {
            strings[n] = "" + (char) ('A' + n);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int n = 0; n < 20; n++) {
            testPutIfAbsent();
            testComputeIfAbsent2Lambda();
        }
    }

    private static void testPutIfAbsent() throws InterruptedException {
        final AtomicLong totalTime = new AtomicLong();
        final ConcurrentHashMap<String, AtomicInteger> map = new ConcurrentHashMap<String, AtomicInteger>();
        final Random random = new Random();
        ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);

        for (int i = 0; i < numberOfThreads; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    long start, end;
                    for (int n = 0; n < numberOfRuns; n++) {
                        String s = strings[random.nextInt(strings.length)];
                        start = System.nanoTime();

                        AtomicInteger count = map.get(s);
                        if (count == null) {
                            count = new AtomicInteger(0);
                            AtomicInteger prevCount = map.putIfAbsent(s, count);
                            if (prevCount != null) {
                                count = prevCount;
                            }
                        }
                        count.incrementAndGet();
                        end = System.nanoTime();
                        totalTime.addAndGet(end - start);
                    }
                }
            });
        }
        executorService.shutdown();
        executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
        System.out.println("Test " + Thread.currentThread().getStackTrace()[1].getMethodName()
                + " average time per run: " + (double) totalTime.get() / numberOfThreads / numberOfRuns + " ns");
    }


     private static void testComputeIfAbsent2Lambda() throws InterruptedException {
            final AtomicLong totalTime = new AtomicLong();
            final ConcurrentHashMap2<String, AtomicInteger> map = new ConcurrentHashMap2<String, AtomicInteger>();
            final Random random = new Random();
            ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);
            for (int i = 0; i < numberOfThreads; i++) {
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        long start, end;
                        for (int n = 0; n < numberOfRuns; n++) {
                            String s = strings[random.nextInt(strings.length)];
                            start = System.nanoTime();

                            AtomicInteger count = map.computeIfAbsent2(s, (k) -> new AtomicInteger(0));
                            count.incrementAndGet();

                            end = System.nanoTime();
                            totalTime.addAndGet(end - start);
                        }
                    }
                });
            }
            executorService.shutdown();
            executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
            System.out.println("Test " + Thread.currentThread().getStackTrace()[1].getMethodName()
                    + " average time per run: " + (double) totalTime.get() / numberOfThreads / numberOfRuns + " ns");
        }

        public static class ConcurrentHashMap2<K,V> extends ConcurrentHashMap<K,V> {

            /**
             * If there is no mapping for the key then computes and puts the mapping,
             * otherwise it simply return the value for that key.
             * In case of concurrent initialization of the same key the mappingFunction can be called more than once.
             * @param key - the key to be initialized or retrieved
             * @param mappingFunction - the function to be called for computation of initial value.
             * @return computed value if the key wasn't already in the map otherwise return the actual value for provided key.
             */
            public V computeIfAbsent2(K key, Function<K,V> mappingFunction) {
                V value = get(key);
                if (value == null) {
                    value = mappingFunction.apply(key);
                    V prevValue = putIfAbsent(key, value);
                    if (prevValue != null) {
                        value = prevValue;
                    }
                }
                return value;
            }
        }
}
Test testComputeIfAbsent2Lambda average time per run: 138.1053415 ns
Test testPutIfAbsent average time per run: 129.45236425 ns
Test testComputeIfAbsent2Lambda average time per run: 128.48006825 ns
Test testPutIfAbsent average time per run: 118.733798375 ns
Test testComputeIfAbsent2Lambda average time per run: 134.038046625 ns
Test testPutIfAbsent average time per run: 119.7947695 ns
Test testComputeIfAbsent2Lambda average time per run: 134.183876375 ns
Test testPutIfAbsent average time per run: 137.969932625 ns
Test testComputeIfAbsent2Lambda average time per run: 137.97531275 ns
Test testPutIfAbsent average time per run: 136.904379125 ns
Test testComputeIfAbsent2Lambda average time per run: 148.899750125 ns
Test testPutIfAbsent average time per run: 129.788293125 ns
Test testComputeIfAbsent2Lambda average time per run: 141.50586625 ns
Test testPutIfAbsent average time per run: 129.081558875 ns
Test testComputeIfAbsent2Lambda average time per run: 122.36628625 ns
Test testPutIfAbsent average time per run: 127.1215535 ns
Test testComputeIfAbsent2Lambda average time per run: 108.129917625 ns
Test testPutIfAbsent average time per run: 133.630786875 ns
Test testComputeIfAbsent2Lambda average time per run: 134.978805625 ns
Test testPutIfAbsent average time per run: 132.7747585 ns
Test testComputeIfAbsent2Lambda average time per run: 132.4352885 ns
Test testPutIfAbsent average time per run: 133.753792875 ns
Test testComputeIfAbsent2Lambda average time per run: 134.09569175 ns
Test testPutIfAbsent average time per run: 145.610141125 ns
Test testComputeIfAbsent2Lambda average time per run: 139.437622125 ns
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;

public class CocnurrentHashMap2PutBenchmark {
    private final static int numberOfRuns = 1000000;
    private final static int numberOfThreads = Runtime.getRuntime().availableProcessors();

    public static void main(String[] args) throws InterruptedException {
        for (int n = 0; n < 20; n++) {
            testComputeIfAbsent2();
            testComputeIfAbsent();
        }
    }

    private static void testComputeIfAbsent2() throws InterruptedException {
        final AtomicLong totalTime = new AtomicLong();
        final ConcurrentHashMap2<Integer, String> map = new ConcurrentHashMap2<Integer, String>();
        ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);
        for (int i = 0; i < numberOfThreads; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    long start, end;
                    for (int n = 0; n < numberOfRuns; n++) {
                        Integer key = Integer.valueOf(n);
                        start = System.nanoTime();

                        String value = map.computeIfAbsent2(key, (k) -> "value");

                        end = System.nanoTime();
                        totalTime.addAndGet(end - start);
                    }
                }
            });
        }
        executorService.shutdown();
        executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
        System.out.println("Test " + Thread.currentThread().getStackTrace()[1].getMethodName()
                + " average time per run: " + (double) totalTime.get() / numberOfThreads / numberOfRuns + " ns");
    }

    private static void testComputeIfAbsent() throws InterruptedException {
        final AtomicLong totalTime = new AtomicLong();
        final ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<Integer, String>();
        ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);
        for (int i = 0; i < numberOfThreads; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    long start, end;
                    for (int n = 0; n < numberOfRuns; n++) {
                        Integer key = Integer.valueOf(n);
                        start = System.nanoTime();

                        String value = map.computeIfAbsent(key, (k) -> "value");

                        end = System.nanoTime();
                        totalTime.addAndGet(end - start);
                    }
                }
            });
        }
        executorService.shutdown();
        executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
        System.out.println("Test " + Thread.currentThread().getStackTrace()[1].getMethodName()
                + " average time per run: " + (double) totalTime.get() / numberOfThreads / numberOfRuns + " ns");
    }

    public static class ConcurrentHashMap2<K, V> extends ConcurrentHashMap<K, V> {

        /**
         * If there is no mapping for the key then computes and puts the
         * mapping, otherwise it simply return the value for that key. In case
         * of concurrent initialization of the same key the mappingFunction can
         * be called more than once.
         * 
         * @param key
         *            - the key to be initialized or retrieved
         * @param mappingFunction
         *            - the function to be called for computation of initial
         *            value.
         * @return computed value if the key wasn't already in the map otherwise
         *         return the actual value for provided key.
         */
        public V computeIfAbsent2(K key, Function<K, V> mappingFunction) {
            V value = get(key);
            if (value == null) {
                value = mappingFunction.apply(key);
                V prevValue = putIfAbsent(key, value);
                if (prevValue != null) {
                    value = prevValue;
                }
            }
            return value;
        }
    }
}
Test testComputeIfAbsent2 average time per run: 445.077932375 ns
Test testComputeIfAbsent average time per run: 784.786391 ns
Test testComputeIfAbsent2 average time per run: 294.10136375 ns
Test testComputeIfAbsent average time per run: 314.8724765 ns
Test testComputeIfAbsent2 average time per run: 236.56533275 ns
Test testComputeIfAbsent average time per run: 350.863664625 ns
Test testComputeIfAbsent2 average time per run: 346.19498275 ns
Test testComputeIfAbsent average time per run: 641.995172625 ns
Test testComputeIfAbsent2 average time per run: 255.441646125 ns
Test testComputeIfAbsent average time per run: 326.399150125 ns
Test testComputeIfAbsent2 average time per run: 275.626666125 ns
Test testComputeIfAbsent average time per run: 201.207314125 ns
Test testComputeIfAbsent2 average time per run: 289.19059725 ns
Test testComputeIfAbsent average time per run: 318.448059 ns
Test testComputeIfAbsent2 average time per run: 225.19701825 ns
Test testComputeIfAbsent average time per run: 306.461814125 ns
Test testComputeIfAbsent2 average time per run: 213.460366 ns
Test testComputeIfAbsent average time per run: 334.325044625 ns
Test testComputeIfAbsent2 average time per run: 256.4048955 ns
Test testComputeIfAbsent average time per run: 256.366700625 ns
Test testComputeIfAbsent2 average time per run: 231.88875575 ns
Test testComputeIfAbsent average time per run: 246.076624 ns
Test testComputeIfAbsent2 average time per run: 222.4649485 ns
Test testComputeIfAbsent average time per run: 266.505719625 ns
Test testComputeIfAbsent2 average time per run: 228.708391375 ns
Test testComputeIfAbsent average time per run: 261.866442625 ns
Test testComputeIfAbsent2 average time per run: 198.614718875 ns
Test testComputeIfAbsent average time per run: 225.43031925 ns
Test testComputeIfAbsent2 average time per run: 300.478359 ns
Test testComputeIfAbsent average time per run: 306.03640225 ns
Test testComputeIfAbsent2 average time per run: 195.0444215 ns
Test testComputeIfAbsent average time per run: 271.461982625 ns
Test testComputeIfAbsent2 average time per run: 224.306529875 ns
Test testComputeIfAbsent average time per run: 334.52790425 ns
Test testComputeIfAbsent2 average time per run: 212.217131625 ns
Test testComputeIfAbsent average time per run: 184.541579125 ns
Test testComputeIfAbsent2 average time per run: 265.417909625 ns
Test testComputeIfAbsent average time per run: 213.9811425 ns
Test testComputeIfAbsent2 average time per run: 298.76602575 ns
Test testComputeIfAbsent average time per run: 347.883728125 ns