ConcurrentHashMap的putIfAbsent方法详解与应用_元一软件

在并发编程中,ConcurrentHashMap是一个常用的线程安全哈希表实现,它提供了多种原子操作方法来简化并发环境下的数据更新。其中,putIfAbsent方法是一个简单而强大的工具,用于在键不存在时插入值,键存在时则不执行任何操作。本文将详细解析putIfAbsent方法的作用、用法、源码逻辑以及与其他相关方法的对比。


putIfAbsent方法的作用

putIfAbsent方法用于在键不存在时原子性地插入值,键存在时则不执行任何操作。这对于需要避免重复插入或初始化的场景非常有用。

方法定义

V putIfAbsent(K key, V value)

核心特性

典型使用场景

简单缓存系统

ConcurrentHashMap<String, Config>configCache=new ConcurrentHashMap<>();public Config getConfig(String configName){Config config=new Config(configName);// 可能创建多余对象 Config existing=configCache.putIfAbsent(configName, config);returnexisting!=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);returnexisting!=null ? existing:list;}

源码逻辑简化

final V putIfAbsent(K key, V value){if(key==null||value==null)throw new NullPointerException();inthash=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();elseif((f=tabAt(tab,i=(n-1)&hash))==null){// 键不存在,尝试插入新节点if(casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))break;}elseif((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)returnoldVal;}}addCount(1L, binCount);returnnull;}

与computeIfAbsent对比

特性putIfAbsentcomputeIfAbsent
触发条件键不存在键不存在或值为null
值创建时机调用前创建(可能多余)函数内按需创建
函数参数Function<K, V>
适用场景对象创建成本低、简单插入延迟初始化、复杂对象创建

最佳实践

完整示例

importjava.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);// 输出:1System.out.println("Current value for 'a': "+ cache.get("a"));// 输出:1}}