如何通过ConcurrentHashMap的computeIfAbsent判断是否首次加载缓存?
问题描述
我需要实现类似Guava或Caffeine中LoadingCache的功能,用ConcurrentHashMap写了如下缓存类:
final class Cache { private static final ConcurrentHashMap<String, List<String>> CACHE = ... static List<String> byName(String name) { return CACHE.computeIfAbsent(name, x -> // 一些耗时操作) } }
现在希望判断调用byName方法时,是首次将数据加载到缓存中,还是读取已有的映射。目前我用如下方式实现:
final class Cache { private static final ConcurrentHashMap<String, List<String>> CACHE = ... static List<String> byName(String name) { boolean b[] = new boolean[1]; List<String> result = CACHE.computeIfAbsent(name, x -> { b[0] = true; // 一些耗时操作 }); if(b[0]) { // 首次加载缓存,执行逻辑X } else { // 执行逻辑Y } return result; } }
这个方式能正常工作,但我想知道ConcurrentHashMap是否提供原生方法实现相同功能,特此请教。
回答
ConcurrentHashMap没有提供原生方法直接判断本次调用是否触发了新值的加载。
你当前使用boolean数组的实现是可靠的,核心是利用computeIfAbsent的原子性:只有当键不存在时,才会执行传入的lambda表达式,因此在lambda内标记状态的逻辑是线程安全的。之所以用数组,是因为lambda只能访问外部的final或有效final变量,数组可以绕开这个限制。
如果想要更具可读性的实现,可以用AtomicBoolean代替数组,原理完全一致:
final class Cache { private static final ConcurrentHashMap<String, List<String>> CACHE = ... static List<String> byName(String name) { AtomicBoolean isFirstLoad = new AtomicBoolean(false); List<String> result = CACHE.computeIfAbsent(name, x -> { isFirstLoad.set(true); // 执行耗时的数据加载操作 return loadExpensiveData(x); }); if (isFirstLoad.get()) { // 首次加载缓存,执行逻辑X } else { // 读取已有缓存,执行逻辑Y } return result; } private static List<String> loadExpensiveData(String key) { // 具体的耗时加载逻辑 } }
需要注意的是,不要尝试用containsKey先判断再调用computeIfAbsent的组合方式——这两个操作之间没有原子性保证,并发场景下会出现误判:比如线程A调用containsKey返回false,随后线程B抢先执行computeIfAbsent完成加载,此时线程A再执行computeIfAbsent不会触发加载,但之前的containsKey判断会错误地认为是首次加载,导致逻辑出错。
内容的提问来源于stack exchange,提问作者Eugene
相关产品推荐
相关产品推荐

