文档章节

并发容器ConcurrentHashMap

秋风醉了
 秋风醉了
发布于 2016/09/18 16:13
字数 1823
阅读 35
收藏 1

并发容器ConcurrentHashMap

ConcurrentHashMap概述

与HashMap 一样,ConcurrentHashMap也是一个基于散列的Map,但它使用了一种完全不同的加锁策略来提供更高的并发性和伸缩性。ConcurrentHashMap并不是将每个方法都在同一个锁上同步并使得每次只能有一个线程访问容器,而是使用一种粒度更细的加锁机制来实现更大程度的共享,这种机制称为分段锁。

在这种机制中,任意数量的读取线程可以并发的访问Map,执行读取操作和执行写入操作的线程可以并发访问Map,并且一定数量的写入线程可以并发的修改Map。ConcurrentHashMap带来的结果是,在并发访问环境下将实现更高的吞吐量,而在单线程环境中只损失非常小的性能。

ConcurrentHashMap 与其它并发容器增强了同步容器类:它提供的迭代器不会抛出ConcurrentModificationException,因此不需要在迭代过程中对容器加锁。ConcurrentHashMap 返回的迭代器具有弱一致性,而并非及时失败。弱一致性的迭代器可以容忍并发的修改,当创建迭代器时会遍历已有的元素,并可以(但是不保证)在迭代器被构造后将修改操作反映给容器。

对于一个需要在整个Map上进行计算的方法,例如size和isEmpty,这个方法的语义被略微减弱了以反映容器的并发特性。由于size返回的结果在计算时可能已经过期了,它实际上只是一个估计值,因此允许size返回一个近似值而不是一个精确值。

ConcurrentHashMap内部结构

对于哈希表,Java中采用链表的方式来解决hash冲突的。一个HashMap的数据结构看起来类似下图:

输入图片说明

实现了同步的HashTable也是这样的结构,它的同步使用锁来保证的,并且所有同步操作使用的是同一个锁对象。这样若有n个线程同时在get时,这n个线程要串行的等待来获取锁。

ConcurrentHashMap中对这个数据结构,针对并发稍微做了一点调整。它把区间按照并发级别(ConcurrentLevel),分成了若干个segment。默认情况下内部按并发级别为16来创建。创建好默认的ConcurrentHashMap之后,它的结构大致如下图:

输入图片说明

继续看每个segment是怎么定义的:

static final class Segment<K,V> extends ReentrantLock implements Serializable

Segment继承了ReentrantLock,表明每个segment都可以当做一个锁。这样对每个segment中的数据需要同步操作的话都是使用每个segment容器对象自身的锁来实现。只有对全局需要改变时锁定的是所有的segment。

ConcurrentHashMap原子操作

由于 ConcurrentHashMap 不能被加锁来执行独占访问,因此我们无法使用客户端加锁来创建新的原子操作。但是一些常见的复合操作,例如“若没有则添加”、“若想等则移除”和“若想等则替换”等,都已经实现为原子操作并且在ConcurrentMap的接口中声明。如果你需要考虑在现有的同步Map中添加这样的功能,那么很有可能应该考虑使用ConcurrentMap了。

public interface ConcurrentMap<K, V> extends Map<K, V> {
    /**
     * If the specified key is not already associated
     * with a value, associate it with the given value.
     * This is equivalent to
     * <pre>
     *   if (!map.containsKey(key))
     *       return map.put(key, value);
     *   else
     *       return map.get(key);</pre>
     * except that the action is performed atomically.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with the specified key, or
     *         <tt>null</tt> if there was no mapping for the key.
     *         (A <tt>null</tt> return can also indicate that the map
     *         previously associated <tt>null</tt> with the key,
     *         if the implementation supports null values.)
     * @throws UnsupportedOperationException if the <tt>put</tt> operation
     *         is not supported by this map
     * @throws ClassCastException if the class of the specified key or value
     *         prevents it from being stored in this map
     * @throws NullPointerException if the specified key or value is null,
     *         and this map does not permit null keys or values
     * @throws IllegalArgumentException if some property of the specified key
     *         or value prevents it from being stored in this map
     *
     */
    V putIfAbsent(K key, V value);

    /**
     * Removes the entry for a key only if currently mapped to a given value.
     * This is equivalent to
     * <pre>
     *   if (map.containsKey(key) &amp;&amp; map.get(key).equals(value)) {
     *       map.remove(key);
     *       return true;
     *   } else return false;</pre>
     * except that the action is performed atomically.
     *
     * @param key key with which the specified value is associated
     * @param value value expected to be associated with the specified key
     * @return <tt>true</tt> if the value was removed
     * @throws UnsupportedOperationException if the <tt>remove</tt> operation
     *         is not supported by this map
     * @throws ClassCastException if the key or value is of an inappropriate
     *         type for this map
     *         (<a href="../Collection.html#optional-restrictions">optional</a>)
     * @throws NullPointerException if the specified key or value is null,
     *         and this map does not permit null keys or values
     *         (<a href="../Collection.html#optional-restrictions">optional</a>)
     */
    boolean remove(Object key, Object value);

    /**
     * Replaces the entry for a key only if currently mapped to a given value.
     * This is equivalent to
     * <pre>
     *   if (map.containsKey(key) &amp;&amp; map.get(key).equals(oldValue)) {
     *       map.put(key, newValue);
     *       return true;
     *   } else return false;</pre>
     * except that the action is performed atomically.
     *
     * @param key key with which the specified value is associated
     * @param oldValue value expected to be associated with the specified key
     * @param newValue value to be associated with the specified key
     * @return <tt>true</tt> if the value was replaced
     * @throws UnsupportedOperationException if the <tt>put</tt> operation
     *         is not supported by this map
     * @throws ClassCastException if the class of a specified key or value
     *         prevents it from being stored in this map
     * @throws NullPointerException if a specified key or value is null,
     *         and this map does not permit null keys or values
     * @throws IllegalArgumentException if some property of a specified key
     *         or value prevents it from being stored in this map
     */
    boolean replace(K key, V oldValue, V newValue);

    /**
     * Replaces the entry for a key only if currently mapped to some value.
     * This is equivalent to
     * <pre>
     *   if (map.containsKey(key)) {
     *       return map.put(key, value);
     *   } else return null;</pre>
     * except that the action is performed atomically.
     *
     * @param key key with which the specified value is associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with the specified key, or
     *         <tt>null</tt> if there was no mapping for the key.
     *         (A <tt>null</tt> return can also indicate that the map
     *         previously associated <tt>null</tt> with the key,
     *         if the implementation supports null values.)
     * @throws UnsupportedOperationException if the <tt>put</tt> operation
     *         is not supported by this map
     * @throws ClassCastException if the class of the specified key or value
     *         prevents it from being stored in this map
     * @throws NullPointerException if the specified key or value is null,
     *         and this map does not permit null keys or values
     * @throws IllegalArgumentException if some property of the specified key
     *         or value prevents it from being stored in this map
     */
    V replace(K key, V value);
}

锁分段和ConcurrentHashMap

在ConcurrentHashMap的实现中使用了一个包含16个锁的数组,每个锁保护所有散列桶的1/16,其中第N个散列桶由第(N mod 16)个锁来保护。假设散列函数具有合理的分布性,并且关键字能够实现均匀分布,那么这大约能把对于锁的请求减少到原来的1/16。这是这项技术使得ConcurrentHashMap能够支持多达16个并发的写入器。

锁分段的一个劣势在于:与采用单个锁来实现独占访问相比,要获取多个锁来实现独占访问将更加困难并且开销更高。

通常在执行一个操作时最多只需要获取一个锁,但在某些情况下需要加锁整个容器,例如当ConcurrentHashMap需要扩展映射范围,以及需要重新计算键值的散列值要分布到更大的桶集合时,就需要获取分段锁集合中所有的锁。

参考:

http://www.cnblogs.com/yydcdut/p/3959815.html

http://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/index.html

==========END==========

© 著作权归作者所有

秋风醉了
粉丝 253
博文 532
码字总数 405694
作品 0
朝阳
程序员
私信 提问
Java多线程-并发容器

Java多线程-并发容器 在Java1.5之后,通过几个并发容器类来改进同步容器类,同步容器类是通过将容器的状态串行访问,从而实现它们的线程安全的,这样做会消弱了并发性,当多个线程并发的竞争...

tianfuguoss
2015/12/01
204
0
3.JUC线程高级-同步容器 ConcurrentHashMap

Java5.0 在java.util.concurrent 包中提供了多种并发容器类来改进同步容器的性能。 ConcurrentHashMap 同步容器类是Java5 增加的一个线程安全的哈希表。对于多线程的操作,介于HashMap与Has...

潘天涯
2018/09/04
0
0
ConcurrentHashMap与容器的线程安全

Java集合框架的容器类绝大部分不是线程安全的,仅有的线程安全实现,比如Vector、Stack,在性能方面也远不尽如人意。幸好Java语言提供了并发包(java.util.concurrent),为高度并发需求提供...

琚建飞
03/12
0
0
Java并发(9)- 从同步容器到并发容器

引言 容器是Java基础类库中使用频率最高的一部分,Java集合包中提供了大量的容器类来帮组我们简化开发,我前面的文章中对Java集合包中的关键容器进行过一个系列的分析,但这些集合类都是非线...

Ala6
2018/10/17
93
0
10《Java核心技术》之如何保证集合是线程安全的? ConcurrentHashMap如何实现高效地线程安全?

一、提出问题 之前我们一起讨论过两讲 Java 集合框架的典型容器类,它们绝大部分都不是线程安全的,仅有的线程安全实现,比如 Vector、Stack,在性能方面也远不尽如人意。幸好 Java 语言提供...

飞鱼说编程
2018/10/18
62
0

没有更多内容

加载失败,请刷新页面

加载更多

目标检测中 yolo 的mAP是什么含义?

mAP定义及相关概念 P => precision,即 准确率 R => recall,即 召回率 PR曲线 = >即 以 precision 和 recall 作为 纵、横轴坐标 的二维曲线。一般来说,precision 和 recall 是 鱼与熊掌 的...

小松1
4分钟前
1
0
用jdk1.8的断言来做非空判断

Assert.notNull(user, "没有获得登录用户信息"); 看源码如下: public static void notNull(Object object, String message) { if (object == null) { throw new IllegalArgum......

architect刘源源
9分钟前
2
0
免费节假日api每一时间更新 2020年 部分节假日安排

根据国务院办公厅关于2020年部分节假日安排的通知国办发明电〔2019〕16号.免费节假日api每一时间更新 2020年 部分节假日安排 http://tool.bitefu.net/jiari/ 各省、自治区、直辖市人民政府,...

xiaogg
12分钟前
3
0
2018NOIP各省一等奖分数线

提高组 普及组

SamXIAO
21分钟前
5
0
常见的PPT时间轴怎么制作,这几种方法你要知道

在PPT当中,时间轴是一个非常重要的一个版块,很多PPT会用它来表示公司的发展历程和项目进度。但是对于PPT时间轴的制作很多人做法是一条直线上添几个点,标注出事件就完成了,可是这样也太过...

TeFuiro
27分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部