Играя с 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()), но его выполнение выполняется в два раза быстрее (5010ms для testCase3 против 10068ms для testCase1).
Рассматривая исходный код, он почти такой же как для computeIfAbsent(), так и для putVal() (который используется в putIfAbsent() под капотом).
Кто-нибудь знает, что вызывает различное время выполнения потоков?
Вы ничего не измеряете. Сделайте правильную отметку с помощью микросхемы с помощью JMH. –
Перед тестированием необходимо подкрепить код. Запускайте каждый тест несколько раз в течение как минимум 10 секунд и игнорируйте эти результаты (только считайте те, которые появляются после) –
'putIfAbsent()' уже имеет объект. 'computeIfAbsent()' должен выполнить метод для определения объекта. Все это документировано. – EJP