自 JDK 1.5 以来,Java 提供了 ConcurrentMap 接口,用于实现线程安全的 Map。该接口是 Map 的子接口,官方注释如下:
A Map providing thread safety and atomicity guarantees.
Memory consistency effects: As with other concurrent collections, actions in a thread prior to placing an object into a
ConcurrentMap
as a key or value happen-before actions subsequent to the access or removal of that object from theConcurrentMap
in another thread.This interface is a member of the Java Collections Framework.
大概意思是说:
ConcurrentMap 是一个提供线程安全性和原子性保证的 Map。
内存一致性影响:与其他并发集合一样,将对象作为键或值放入 ConcurrentMap 的操作先于另一个线程中从ConcurrentMap中访问或删除该对象的操作。
该接口是Java Collections Framework的成员。
接下来将通过源码对比 Map 接口和 ConcurrentMap 接口的方法差异。(注:该源码是基于 JDK 1.8)
1.1 getOrDefault() 方法
Map
default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
ConcurrentMap
@Override
default V getOrDefault(Object key, V defaultValue) {
V v;
return ((v = get(key)) != null) ? v : defaultValue;
}
区别:由于 ConcurrentMap 不允许 value 为空,所以如果 get 到的 value 为空,说明 key 不存在,直接返回默认值,而 Map 是允许 value 为空的,所以如果 key 存在,而value 为空,将返回 null,而不是默认值。
1.2 forEach() 方法
Map
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// 抛出该异常意味着该 Entry 已经不在 Map 中了
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
ConcurrentMap
@Override
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// 抛出该异常意味着该 Entry 已经不在 Map 中了
continue;
}
action.accept(k, v);
}
}
区别:Map 的 forEach 方法在遇到 IllegalStateException 异常时,将会抛出 ConcurrentModificationException 异常,终止遍历,而 ConcurrentMap 则只是结束本次遍历,继续下一次遍历,而不会向上抛出异常。
1.3 replaceAll() 方法
Map
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// 抛出该异常意味着该 Entry 已经不在 Map 中了
throw new ConcurrentModificationException(ise);
}
// 该方法抛出的 IllegalStateException 异常不是 ConcurrentModificationException
v = function.apply(k, v);
try {
entry.setValue(v);
} catch(IllegalStateException ise) {
// 抛出该异常意味着该 Entry 已经不在 Map 中了
throw new ConcurrentModificationException(ise);
}
}
}
ConcurrentMap
@Override
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
forEach((k,v) -> {
while(!replace(k, v, function.apply(k, v))) {
// v 已经被修改或者 k 已经被移除
if ( (v = get(k)) == null) {
// k 在 map 中已经不存在了
break;
}
}
});
}
区别:Map 在遇到 IllegalStateException 异常时,都会向上抛出(抛出 ConcurrentModificationException 或者 IllegalStateException 异常)从而终止操作。而 ConcurrentMap 则不会抛出异常。
1.4 computeIfAbsent() 方法
Map
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}
return v;
}
ConcurrentMap
@Override
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v, newValue;
return ((v = get(key)) == null &&
(newValue = mappingFunction.apply(key)) != null &&
(v = putIfAbsent(key, newValue)) == null) ? newValue : v;
}
区别:ConcurrentMap 需要满足 key 对应的 value 为空,并且执行 mappingFunction 方法返回值不为空,才会将 mappingFunction 返回值调用 putIfAbsent 方法,当这一步操作成功后,将 newValue 返回,否则返回 null;而 Map 中调用的是 put 方法,意味着在多线程环境下,可能出现一个值被多次替换的情况。
1.5 computeIfPresent() 方法
Map
default V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue;
if ((oldValue = get(key)) != null) {
V newValue = remappingFunction.apply(key, oldValue);
if (newValue != null) {
put(key, newValue);
return newValue;
} else {
remove(key);
return null;
}
} else {
return null;
}
}
ConcurrentMap
@Override
default V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue;
// 循环判断 key 是否存在
while((oldValue = get(key)) != null) {
V newValue = remappingFunction.apply(key, oldValue);
if (newValue != null) {
// 如果替换失败,说明有其他线程修改了原 entry,则重试
if (replace(key, oldValue, newValue))
return newValue;
} else if (remove(key, oldValue))
return null;
}
return oldValue;
}
区别:ConcurrentMap 在该方法中加了一个循环来判断 key 对应的 value 是否为空(类似自旋锁),如果不为空则对该值进行替换,然后获得 remappingFunction 执行后的返回值,如果返回值 newValue 不为空,则替换 key 对应的 value 为 newValue,否则就移除该 key-value(注意:替换和移除操作的实现方法都需要保证线程同步);而 Map 在多线程环境下无法保证线程安全,还会出现多次替换值的情况。
1.6 compute() 方法
Map
default V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue = get(key);
V newValue = remappingFunction.apply(key, oldValue);
if (newValue == null) {
// 如果 newValue 为空,则移除该 entry
if (oldValue != null || containsKey(key)) {
// 移除原 key-value
remove(key);
return null;
} else {
// 不做操作
return null;
}
} else {
// 添加或者替换原 value
put(key, newValue);
return newValue;
}
}
ConcurrentMap
@Override
default V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue = get(key);
for(;;) {
V newValue = remappingFunction.apply(key, oldValue);
if (newValue == null) {
// 如果 newValue 为空,则移除该 entry
if (oldValue != null || containsKey(key)) {
// 原值存在,则移除
if (remove(key, oldValue)) {
// 正常移除,没有抛出异常
return null;
}
// 其他线程修改了 value 值,则重试
oldValue = get(key);
} else {
// 不做操作
return null;
}
} else {
// 添加或者替换原来的 entry
if (oldValue != null) {
// 替换
if (replace(key, oldValue, newValue)) {
// 正常替换,没有抛出异常
return newValue;
}
// 其他线程修改了 value 值,则重试
oldValue = get(key);
} else {
// 如果 oldValue 为空,则说明是新增操作,调用 putIfAbsent 方法,可以保证不会多次替换
if ((oldValue = putIfAbsent(key, newValue)) == null) {
// 替换成功
return newValue;
}
// 其他线程修改了 oldValue,重试
}
}
}
}
区别:ConcurrentMap 同样添加了自旋锁,如果对 map 操作失败了,则说明有其他线程已经对 map 进行了修改,则进行重试,以此来保证线程安全,另外,替换值的时候,调用 putIfAbsent() 方法,保证不会对 entry 进行多次替换;而 Map 则没有保证线程安全,另外调用 putVal 在多线程环境下也可能出现 entry 被多次替换。
1.7 merge() 方法
Map
default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value);
V oldValue = get(key);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if(newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}
ConcurrentMap
@Override
default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value);
V oldValue = get(key);
for (;;) {
if (oldValue != null) {
V newValue = remappingFunction.apply(oldValue, value);
if (newValue != null) {
if (replace(key, oldValue, newValue))
return newValue;
} else if (remove(key, oldValue)) {
return null;
}
oldValue = get(key);
} else {
if ((oldValue = putIfAbsent(key, value)) == null) {
return value;
}
}
}
}
区别:与上面一样,ConcurrentMap 用自旋锁来保证 replace 和 remove 操作的线程安全;而 Map 则在多线程环境下无法保证安全性。
综上,ConcurrentMap 接口的默认方法内,部分方法是调用虚方法来实现的,而这些方法的线程安全性依赖其实现类的实现方法。一部分方法在ConcurrentMap 默认方法内使用自旋锁来保证线程安全。