我在玩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为5010ms,而testCase1为10068ms)。
查看源代码,computeIfAbsent()和putVal()(在底层的putIfAbsent()中使用)几乎相同。
有人知道导致线程执行时间不同的原因吗?
最佳答案
您遇到了已记录的功能:
在进行计算时,可能会阻止其他线程在此映射上进行的某些尝试的更新操作,因此计算应简短而简单,并且不得尝试更新此映射的任何其他映射。
computeIfAbsent检查密钥是否存在并锁定地图的某些部分。然后,它调用functor并将结果放入map中(如果返回的值不为null)。只有在那之后,地图的这一部分才被解除阻止。
另一方面,test3始终调用c.call(),在计算结束后,它将调用putIfAbsent。