假设有一个需求:实时统计某个网址的访问次数
方案一:利用concurrentHashMap,进行统计
private Map<String, Long> wordCounts = new ConcurrentHashMap<>();
public long increase(String word) {
Long oldValue = wordCounts.get(word);
Long newValue = (oldValue == null) ? 1L : oldValue + 1;
wordCounts.put(word, newValue);
return newValue;
}
在多个线程环境increase()的统计是错误的,因为多个线程用相同的word调用时,很可能会覆盖相互的结果,造成记录的次数比实际出现的次数少。concurrentHashMap 能保证的是每一个操作(put,get,delete…)本身是线程安全的,但是increase是先取、再加多个步骤。
对上面increase方法加锁可以解决问题,但是会降低并发量。
方案二:借助ConcurrentMap接口定义了几个基于 CAS 操作:
private ConcurrentMap<String, Long> wordCounts = new ConcurrentHashMap<>();
public long increase(String word) {
Long oldValue, newValue;
while (true) {
oldValue = wordCounts.get(word);
if (oldValue == null) {
// Add the word firstly, initial the value as 1
newValue = 1L;
if (wordCounts.putIfAbsent(word, newValue) == null) {
break;
}
} else {
newValue = oldValue + 1;
if (wordCounts.replace(word, oldValue, newValue)) {
break;
}
}
}
return newValue;
}
该方法有点复杂,主要因为ConcurrentMap中不能保存value为null的值,所以得同时处理word不存在和已存在两种情况。此外,这里介绍一下ConcurrentHashMap中的常用cas方法(原子操作):
1)putIfAbsent(k,v):
- k不存在:插入,返回null;
- 否则(k存在):返回原值
2)replace(k,v1,v2):
- k存在 && 原值等于v1,则更新v1为v2,返回true;
- 否则,返回false;
方案三:使用ConcurrentHashMap和AtomicLong:
private final ConcurrentMap<String, AtomicLong> wordCounts = new ConcurrentHashMap<>();
public long increase(String word) {
AtomicLong number = wordCounts.get(word);
if (number == null) {
AtomicLong newNumber = new AtomicLong(0);
number = wordCounts.putIfAbsent(word, newNumber);
if (number == null) {
number = newNumber;
}
}
return number.incrementAndGet();
}
这个实现仍然有一处需要说明的地方,如果多个线程同时增加一个目前还不存在的词,那么很可能会产生多个newNumber对象,但最终只有一个newNumber有用,其他的都会被扔掉。对于这个应用,这不算问题,创建AtomicLong的成本不高,而且只在添加不存在词是出现。但是对于缓存场景(缓存创建成本高),就有些问题了。
方案四:使用AtomicLongMap:
AtomicLongMap是Google Guava项目的一个类,它是线程安全、支持并发访问的。对于计数推荐使用该类。
private AtomicLongMap<String> urlCoutn = AtomicLongMap.create();
public long increase(String url) {
long newValue = urlCoutn.incrementAndGet(url);
return newValue;
}
public Long getCount(String url) {
return urlCoutn.get(url);
}
参考
来源:CSDN
作者:赶路人儿
链接:https://blog.csdn.net/liuxiao723846/article/details/103712690