技巧💡
建议使用guava的AtomicLongMap。底层是使用 ConcurrentHashMap + compute方法
统计文本中单词出现的次数,把单词出现的次数记录到一个Map中。
ConcurrentHashMap<String, Long>
先get再put(不可用)
// 如果多个线程并发调用这个increase()方法,increase()的实现就是错误的,因为多个线程用相同的word调用时,很可能会覆盖相互的结果,造成记录的次数比实际出现的次数少。
private final 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;
}
这个主要的问题在于,get虽然是线程安全,但是get和put没有原子性。解决方案,要么加锁,要么cas
加锁
private final Map<String, Long> wordCounts = new ConcurrentHashMap<>();
public synchronized long increase(String word) {
Long oldValue = wordCounts.get(word);
Long newValue = (oldValue == null) ? 1L : oldValue + 1;
wordCounts.put(word, newValue);
return newValue;
}
cas
private final 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;
}
实现每次调用都会涉及Long对象的拆箱和装箱操作,很明显,更好的实现方式是采用AtomicLong
使用compute方法 类似cas
private final Map<String, Long> wordCounts = new ConcurrentHashMap<>();
public synchronized long increase(String word) {
wordCounts.compute(wordCounts, (k, v) -> (v == null) ? 1 : v+1);
return newValue;
}
底层也是用cas。
ConcurrentMap<String, AtomicLong>
private final ConcurrentMap<String, AtomicLong> wordCounts = new ConcurrentHashMap<>();
public long increase(String word) {
AtomicLong number = wordCounts.get(word);
if (number == null) {
// 存在问题。但是单实例doublecheck的问题
AtomicLong newNumber = new AtomicLong(0);
number = wordCounts.putIfAbsent(word, newNumber);
if (number == null) {
number = newNumber;
}
}
return number.incrementAndGet();
}
AtomicLongMap
源码实现-早期版本(cas)
@CanIgnoreReturnValue
public long addAndGet(K key, long delta) {
AtomicLong atomic;
label23:
do {
atomic = (AtomicLong)this.map.get(key);
if (atomic == null) {
atomic = (AtomicLong)this.map.putIfAbsent(key, new AtomicLong(delta));
if (atomic == null) {
return delta;
}
}
long oldValue;
long newValue;
do {
oldValue = atomic.get();
if (oldValue == 0L) {
continue label23;
}
newValue = oldValue + delta;
} while(!atomic.compareAndSet(oldValue, newValue));
return newValue;
} while(!this.map.replace(key, atomic, new AtomicLong(delta)));
return delta;
}
源码实现-最新版本(compute)
@CanIgnoreReturnValue
public long updateAndGet(K key, LongUnaryOperator updaterFunction) {
checkNotNull(updaterFunction);
return map.compute(
key, (k, value) -> updaterFunction.applyAsLong((value == null) ? 0L : value.longValue()));
}