0
点赞
收藏
分享

微信扫一扫

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

在并发编程中,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
    }
}
举报
0 条评论