简体   繁体   中英

Why ConcurrentHashMap::putIfAbsent is faster than ConcurrentHashMap::computeIfAbsent?

Playing with ConcurrentHashMap, I've found out that computeIfAbsent is twice slower than putIfAbsent. Here the simple test:

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;


public class Test {
    public static void main(String[] args) throws Exception {
        String[] keys = {"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a0", "a01", "a02", "a03", "a04", "a05", "a06", "a07", "a08", "a09", "a00"};

        System.out.println("Test case 1");
        long time = System.currentTimeMillis();
        testCase1(keys);
        System.out.println("ExecutionTime: " + String.valueOf(System.currentTimeMillis() - time));

        System.out.println("Test case 2");
        time = System.currentTimeMillis();
        testCase2(keys);
        System.out.println("ExecutionTime: " + String.valueOf(System.currentTimeMillis() - time));

        System.out.println("Test case 3");
        time = System.currentTimeMillis();
        testCase3(keys);
        System.out.println("ExecutionTime: " + String.valueOf(System.currentTimeMillis() - time));
    }

    public static void testCase1(String[] keys) throws InterruptedException {
        ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();

        List<Thread> threads = new ArrayList<>();

        for (String key : keys) {
            Thread thread = new Thread(() -> map.computeIfAbsent(key, s -> {
                System.out.println(key);
                String result = new TestRun().compute();
                System.out.println("Computing finished for " + key);
                return result;
            }));
            thread.start();
            threads.add(thread);
        }

        for (Thread thread : threads) {
            thread.join();
        }
    }

    public static void testCase2(String[] keys) throws InterruptedException {
        List<Thread> threads = new ArrayList<>();

        for (String key : keys) {
            Thread thread = new Thread(() -> {
                System.out.println(key);
                new TestRun().compute();
                System.out.println("Computing finished for " + key);
            });
            thread.start();
            threads.add(thread);
        }

        for (Thread thread : threads) {
            thread.join();
        }
    }


    public static void testCase3(String[] keys) throws InterruptedException {
        ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();

        List<Thread> threads = new ArrayList<>();

        for (String key : keys) {
            Thread thread = new Thread(() -> {
                Callable<String> c = () -> {
                    System.out.println(key);
                    String result = new TestRun().compute();
                    System.out.println("Computing finished for " + key);
                    return result;
                };

                try {
                    map.putIfAbsent(key, c.call());
                } catch (Exception e) {
                    e.printStackTrace(System.out);
                }
            });
            thread.start();
            threads.add(thread);
        }

        for (Thread thread : threads) {
            thread.join();
        }
    }

}

class TestRun {
    public String compute() {
        try {
            Thread.currentThread().sleep(5000);
        } catch (Exception e) {
            e.printStackTrace(System.out);
        }
        return UUID.randomUUID().toString();
    }
}

Running this test on my laptop, testCase1 (which uses computeIfAbsent()) execution time is 10068ms, for testCase2 (which executes the same stuff but WITHOUT wrapping it into computeIfAbsent()) executions time is 5009ms (of course it varies a bit, but the main trend is like that). The most interesting is testCase3 - it's pretty much the same as testCase1 (except that putIfAbsent() is used instead of computeIfAbsent()), but its execution is twice faster (5010ms for testCase3 vs 10068ms for testCase1).

Looking at the source code, it's pretty much the same for both computeIfAbsent() and putVal() (which is used in putIfAbsent() under the hood).

Does anybody know what's causing the different in threads execution time?

You come across documented feature:

Some attempted update operations on this map by other threads may be blocked while computation is in progress, so the computation should be short and simple, and must not attempt to update any other mappings of this map.

computeIfAbsent checks for key presence and locks some part of map. Then it calls functor and puts result into map (if returned value is not null). Only after that this part of map is unblocked.

On the other side, test3 always calls c.call(), and after computation is over, it calls putIfAbsent.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM