Кеш LoadingCache от apache commons не использует синхронизацию в методе getAll
Я создаю вот такой вот кеш LoadingCache:
LoadingCache<String, String> cache = CacheBuilder.newBuilder().build(new CacheLoader<String, String>() {
@Override
public String load(String o) throws Exception {
System.out.println("load: " + o);
Thread.sleep(2000);
return "value-" + o;
}
@Override
public Map<String, String> loadAll(Iterable<? extends String> keys) throws Exception {
System.out.println("loadAll: " + keys);
Thread.sleep(2000);
Map<String, String> result = new HashMap<>();
for (String key : keys) {
result.put(key, "value-" + key);
}
return result;
}
});
В этом кеше я реализовал методы load и loadAll. В обеих случаях я имитирую задержку сети в 2 секунды с помощью Thread.sleep(2000);.
Теперь попробуем из разных потоков одновременно обратиться к кешу вот таким кодом:
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 2; i++) {
service.submit(() -> {
try {
cache.get("key1");
} catch (ExecutionException e) {
e.printStackTrace();
}
});
service.submit(() -> {
try {
cache.getAll(Arrays.asList("key1", "key2"));
} catch (ExecutionException e) {
e.printStackTrace();
}
});
}
Этот код достает из кеша значения по ключам key1 и key2. Два раза вызывается load и два раза вызывается loadAll. Результат работы теста:
load: key1
loadAll: [key1, key2]
loadAll: [key1, key2]
Метод load работает корректно, загрузка ключа key1 была вызвана только один раз, второй вызов совершил синхронизацию и потом получил значение из кеша.
А вот методы loadAll вообще не синхронизируются. Во-первых, они вызывают загрузку key1, когда этот ключ первее загружает метод load. Во-вторых, метод loadAll даже относительно друг друга не делают синхронизации и вызывают загрузку одних и тех же значений.
Если бы все работало идеально, тогда вывод должен был быть таким:
load: key1
loadAll: [key2]
Метод load один раз загрузил бы key1, а loadAll один раз загрузил key2. Все остальные обращения к кешу должны были синхронизироваться и просто получить значение из кеша.
Как решить эту проблему? Есть ли кеш, где реализована корректно эта логика?
Ответы (1 шт):
Спустя время я могу ответить на свои вопросы из прошлого.
Есть ли кеш, где реализована корректно эта логика?
В данный момент нет. Почему это не сделано в LoadingCache объяснено тут:
https://github.com/google/guava/issues/1711#issuecomment-69740046
Но это не значит, что подобную логику реализовать нельзя. Однажды я создам свой кеш с корректно работающей функцией getAll. Когда сделаю, добавлю тут ссылку на него.