2016-10-17 132 views
1

使用ConcurrentHashMap,我發現computeIfAbsent比putIfAbsent慢兩倍。在這裏,簡單的測試:爲什麼ConcurrentHashMap :: putIfAbsent比ConcurrentHashMap :: computeIfAbsent更快?

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())執行時間是5009毫秒(當然,它有所不同,但主要趨勢是這樣的)。最有趣的是testCase3 - 它與testCase1非常相似(除了使用putIfAbsent()代替computeIfAbsent()),但其執行速度快兩倍(testCase3爲5010ms,而testCase1爲10068ms)。看看源代碼,對於computeIfAbsent()和putVal()(在putIfAbsent()中使用它)幾乎是相同的。

有誰知道是什麼導致了線程執行時間的不同嗎?

+1

你沒有測量任何東西。使用JMH做適當的微基準測試 –

+0

您需要在測試之前設置代碼。每次運行多次測試至少10秒鐘,並忽略這些結果(只計算後面的結果) –

+0

'putIfAbsent()'已經有對象。 'computeIfAbsent()'必須執行一個方法來確定對象。這全部記錄在案。 – EJP

回答

1

你遇到記錄功能:當運算正在進行

一些本地圖由其他線程在嘗試更新操作可能會被封鎖,所以在計算應短而簡單,而且絕不能試圖更新此地圖的任何其他映射。

computeIfAbsent檢查密鑰的存在並鎖定地圖的某些部分。然後它調用functor並將結果放入map(如果返回的值不爲null)。只有在這之後,這部分地圖纔會暢通無阻。

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