在并发编程中,ConcurrentHashMap是一个常用的线程安全哈希表实现,它提供了多种原子操作方法来简化并发环境下的数据更新。其中,putIfAbsent方法是一个简单而强大的工具,用于在键不存在时插入值,键存在时则不执行任何操作。本文将详细解析putIfAbsent方法的作用、用法、源码逻辑以及与其他相关方法的对比。
putIfAbsent方法的作用
putIfAbsent方法用于在键不存在时原子性地插入值,键存在时则不执行任何操作。这对于需要避免重复插入或初始化的场景非常有用。
方法定义
V putIfAbsent(K key, V value)
- 参数:
key:要检查的键。value:若键不存在时插入的值。
- 返回值:
- 返回键之前关联的值(若键不存在则返回
null)。
- 返回键之前关联的值(若键不存在则返回
核心特性
- 原子性:检查键是否存在和插入值的操作是原子的,避免并发冲突。
- 条件插入:仅当键不存在时插入值,否则忽略操作。
- 无函数计算:值需提前创建,可能引发多余对象创建(与
computeIfAbsent对比)。
典型使用场景
简单缓存系统
ConcurrentHashMap<String, Config> configCache = new ConcurrentHashMap<>();
public Config getConfig(String configName) {
Config config = new Config(configName); // 可能创建多余对象
Config existing = configCache.putIfAbsent(configName, config);
return existing != null ? existing : config;
}
避免重复初始化
ConcurrentHashMap<String, List<String>> listCache = new ConcurrentHashMap<>();
public List<String> getList(String key) {
List<String> list = new ArrayList<>();
List<String> existing = listCache.putIfAbsent(key, list);
return existing != null ? existing : list;
}
源码逻辑简化
final V putIfAbsent(K key, V value) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// 键不存在,尝试插入新节点
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
break;
} else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f); // 协助扩容
else {
V oldVal = null;
synchronized (f) { // 锁住当前桶的头节点
if (tabAt(tab, i) == f) {
// 遍历链表或红黑树查找键
for (Node<K,V> e = f;;) {
if (e.hash == hash && ((ek = e.key) == key || key.equals(ek))) {
oldVal = e.val;
break;
}
if ((e = e.next) == null)
break;
}
if (oldVal == null) {
// 键不存在,插入新节点
addNode(tab, hash, key, value, i);
}
}
}
if (oldVal != null)
return oldVal;
}
}
addCount(1L, binCount);
return null;
}
与computeIfAbsent对比
| 特性 | putIfAbsent | computeIfAbsent |
|---|---|---|
| 触发条件 | 键不存在 | 键不存在或值为null |
| 值创建时机 | 调用前创建(可能多余) | 函数内按需创建 |
| 函数参数 | 无 | Function<K, V> |
| 适用场景 | 对象创建成本低、简单插入 | 延迟初始化、复杂对象创建 |
最佳实践
-
避免多余对象创建:若对象创建成本高,优先使用
computeIfAbsent。// 不推荐:可能创建多余对象 Value obj = new Value(); map.putIfAbsent(key, obj); // 推荐:按需创建 map.computeIfAbsent(key, k -> new Value()); -
明确返回值含义:返回值
null可能表示键不存在或原值为null,需结合业务逻辑处理。
完整示例
import java.util.concurrent.ConcurrentHashMap;
public class PutIfAbsentDemo {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> cache = new ConcurrentHashMap<>();
// 插入键值对(键不存在)
Integer oldValue = cache.putIfAbsent("a", 1);
System.out.println("Old value for 'a': " + oldValue); // 输出: null
// 尝试插入已存在的键
oldValue = cache.putIfAbsent("a", 2);
System.out.println("Old value for 'a': " + oldValue); // 输出: 1
System.out.println("Current value for 'a': " + cache.get("a")); // 输出: 1
}
}
