ConcurrentHashMap cas操作以及AtomicLongMap类

情到浓时终转凉″ 提交于 2019-12-26 19:50:15

假设有一个需求:实时统计某个网址的访问次数

方案一:利用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);
	}

参考

https://blog.csdn.net/HEYUTAO007/article/details/61429454

https://blog.csdn.net/u011983531/article/details/65936945

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!