深入了解setifabsent方法

发布时间:2023-05-21

一、setifabsent方法的介绍

setifabsent方法是ConcurrentMap接口中的一种方法,它可以在ConcurrentMap中添加一个键-值对,但只有在该键不存在时,才能添加成功。如果该键已经存在,则setifabsent方法不会添加该键-值对。

/**
 * 如果指定的键不存在,则将指定的值与键关联。如果该键已经存在,则此方法不执行任何操作并返回当前的值。
 *
 * @param key 键
 * @param value 值
 * @return 可能为null的之前的价值,如果没有值映射,则为null
 */
V putIfAbsent(K key, V value);

put方法的不同点在于setifabsent方法只有在键不存在时才会添加值,而put方法无论键是否存在都会添加。

二、setifabsent方法的使用场景

setifabsent方法最适用于多线程应用场景,特别是在ConcurrentMap中,多线程同时访问并修改同一个键时,使用setifabsent方法可以保证操作的原子性,避免多个线程同时添加相同的键-值对。 另外,setifabsent方法的返回值为之前该键的旧值,如果该键不存在,则返回null。这个特性可以用于实现一些有用的功能,比如计数器和缓存。

三、使用示例

1. 线程安全的计数器

假设我们需要统计一个网站的访问次数,考虑以下这段代码:

ConcurrentHashMap<String, Integer> counter = new ConcurrentHashMap<>();
public void count(String page) {
    if (counter.containsKey(page)) {
        counter.put(page, counter.get(page) + 1);
    } else {
        counter.put(page, 1);
    }
}

count方法中使用了containsKeyputget方法,虽然这段代码看起来没什么问题,但由于ConcurrentHashMap是一个线程安全的容器,在多线程并发访问的情况下,会存在竞态条件,从而导致结果不准确。 为了保证计数器的正确性,我们可以使用setifabsent方法,将上面的代码修改为以下形式:

ConcurrentHashMap<String, Integer> counter = new ConcurrentHashMap<>();
public void count(String page) {
    Integer count = counter.putIfAbsent(page, 1);
    if (count != null) {
        counter.put(page, count + 1);
    }
}

在使用setifabsent方法后,只有一个线程可以添加相同的键-值对,其他线程会被阻塞,直到添加完成,这样计数器的结果就是准确的了。

2. 线程安全的缓存

如果我们需要实现一个缓存,在多线程并发访问的情况下,也需要保证缓存的正确性和性能。假设我们使用ConcurrentHashMap作为缓存容器,代码如下:

ConcurrentHashMap<String, String> cache = new ConcurrentHashMap<>();
public String get(String key) {
    String value = cache.get(key);
    if (value == null) {
        value = expensiveOperation(key);
        cache.put(key, value);
    }
    return value;
}
private String expensiveOperation(String key) {
    // some expensive operation
}

get方法中使用了getputnull判断,这段代码在单线程环境下没有问题,但在多线程并发访问的情况下,会存在竞态条件,从而导致缓存结果不准确。 为了保证缓存的正确性和性能,我们可以使用setifabsent方法,将上面的代码修改为以下形式:

ConcurrentHashMap<String, String> cache = new ConcurrentHashMap<>();
public String get(String key) {
    String value = cache.get(key);
    if (value == null) {
        String newValue = expensiveOperation(key);
        value = cache.putIfAbsent(key, newValue);
        if (value == null) {
            value = newValue;
        }
    }
    return value;
}
private String expensiveOperation(String key) {
    // some expensive operation
}

在使用setifabsent方法后,只有一个线程可以添加相同的键-值对,其他线程会被阻塞,直到添加完成,这样缓存的结果就是准确的了。另外,由于ConcurrentHashMap是线程安全的,所以使用setifabsent方法可以保证缓存的性能。