一、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
方法中使用了containsKey
、put
和get
方法,虽然这段代码看起来没什么问题,但由于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
方法中使用了get
、put
和null
判断,这段代码在单线程环境下没有问题,但在多线程并发访问的情况下,会存在竞态条件,从而导致缓存结果不准确。
为了保证缓存的正确性和性能,我们可以使用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
方法可以保证缓存的性能。