簡體   English   中英

為什么 ConcurrentHashMap::putIfAbsent 比 ConcurrentHashMap::computeIfAbsent 快?

[英]Why ConcurrentHashMap::putIfAbsent is faster than ConcurrentHashMap::computeIfAbsent?

使用 ConcurrentHashMap,我發現computeIfAbsent 比putIfAbsent 慢兩倍。 這里是簡單的測試:

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();
    }
}

在我的筆記本電腦上運行這個測試,testCase1(它使用 computeIfAbsent())執行時間是 10068ms,testCase2(它執行相同的東西但沒有將它包裝到 computeIfAbsent())執行時間是 5009ms(當然它有點變化,但是主要趨勢是這樣)。 最有趣的是 testCase3 - 它與 testCase1 幾乎相同(除了使用 putIfAbsent() 而不是 computeIfAbsent()),但它的執行速度要快兩倍(testCase3 為 5010 毫秒,而 testCase1 為 10068 毫秒)。

查看源代碼,對於computeIfAbsent() 和putVal()(在引擎蓋下的putIfAbsent() 中使用)幾乎相同。

有人知道是什么導致線程執行時間不同嗎?

您遇到了記錄在案的功能:

在計算過程中,其他線程對該映射的一些嘗試更新操作可能會被阻塞,因此計算應該簡短且簡單,並且不得嘗試更新該映射的任何其他映射。

computeIfAbsent 檢查密鑰是否存在並鎖定地圖的某些部分。 然后它調用函子並將結果放入映射中(如果返回值不為空)。 只有在此之后地圖的這一部分才會被解鎖。

另一方面,test3總是調用c.call(),計算結束后調用putIfAbsent。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM