探究 ConcurrentMap 接口与 Map 接口的区别

自 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 the ConcurrentMap 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 默认方法内使用自旋锁来保证线程安全。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值