前言:ConcurrentHashMap是非常经典的一个类,面试中会被经常问到,因为它里面用了非常复杂的数据结构,设计上也非常精致,同时又涉及并发编程,可以说是个宝藏类,我会尝试解读一下这个类。
(我会抽空一直更新)
它的代码高达6300行
一 注释
我们来看一下类的注释:
上面贴出来的是第一段注释,后面我就不贴图了,直接翻译:
第一段:一个支持完全并发读和高期望并发更新的hash表,这个类和HashTable实现了同样的功能,包含了HashTable中所有的方法。然而,尽管(这个类)所有的操作都是线程安全的,取数据操作没有使用锁,而且不支持锁住整个表(即阻止所有对表的操作)。在依赖这个类的线程安全同时又不依赖它的实现细节的程序中,它和HashTable是可以互操作的。
第二段:取数据操作(包括get())通常不会阻塞,所以可能会和更新操作(包括put()和remove())重叠。取数据反映最近结束的更新操作的结果。(对一个给定的key的更新操作忍受了一个在更新之前发生的关系——这个关系是和任意的取这个数据的操作,(这一段不知道在说什么,建议大家自己看))。对于批量化的操作例如putAll和clear,并发的取操作可能会反映出仅仅一小部分的entry(基本单位)的插入或者删除。相似地,Iterators, Spliterators 和 Enumerations返回在一定程度上反映hash表在或者自从iterator/enumeration创建时的状态的元素,他们不抛出ConcurrentModificationException异常。然而,iterators(迭代器)被设计为一次只能供一个线程去使用。记住,用来汇总状态的方法(包括size,isEmpty,containsValue)返回的结果,只在该map不在其它的线程中进行并发更新的时候才有用,否则,这些方法返回的结果反映了短暂的状态,这些状态足够用于监控或者评估目的,但是不够用于程序控制。
如果有太多的碰撞(例如,拥有不同hash值的key,映射到了同一个slot),表会动态扩张,以实现一个期望的均值上的效果——维持大约一个mapping中有两个bin(等价于装载因子为0.75),在这个均值附近,方差可能会很大,因为mappings(匹配)被添加和删除,但是总体上来说,这样做维持了一个普遍可以接受的时间和空间上的均衡。然而,改变这个或者其它hash表的规模可能时一个相对较慢的操作。如果可能的话,提供一个hash表大小的评估,作为这个类的构造函数的可续参数initialCapacity的初值,是一个非常好的做法。另一个可选的参数loadFactor(装载因子)提供了进一步定制初始table的大小的方法——通过具体化table的密度,密度被用来计算空间的大小,这些空间被分配给定的个数的元素。为了和这个类的以前的版本兼容,它的构造函数可能会有选择的指定concurrencyLevel参数,作为额外的反映table内部大小的线索。注意,使用很多具有相同hash值的key一定会降低任意hash表的表现。为了改进影响,当key是可比较的,这个类可能会在key之间使用比较的顺序来打破关系。
2019.10.14更新
ConcurrentHashMap的Set映射可能是使用newKeySet()或者newKeySet(int)创建的,或者使用keySet(Object)观测当keys是我们感兴趣的,而且映射的values是没有(或许暂时的)使用的或者都映射了相同的value。
ConcurrentHashMap可以被用来作为尺寸可变的频繁map(一种直方图或者多集的模式),通过使用LongAdder类的值并且使用couputeIfAbsent进行初始化。例如,为了向ConcurrentHashMap<String,LongAdder> freqs添加一个值(就是说把它加1),你可以使用:
freqs.computeIfAbsent(k -> new LongAdder()).increment();
这个类和它的views以及iterators实现了Map和Iterator接口的所有的可选的方法。
像Hashtable但是不像HashMap,这个类不允许null值作为key或者value.
ConcurrentHashMap支持一系列连续而且平行的桶操作,不像大部分的Stream方法,这些桶操作被设计为安全而且通常是能够感受到的,甚至被用于被其它线程同步更新的maps;例如,当计算一个共享的注册表中的值的快照和。有三种操作,每一种操作有四种形式,接受使用Keys,Values,Entries和(Key,Value)作为参数和/或返回值的函数。因为ConcurrentHashMap的元素没有按照特定的方法进行排序,而且可能在不同的平行的执行中被按照不同的顺序处理。函数的正确性不应该依赖任何顺序,或者任何其它的当计算在发生时可能短暂改变对象或者值。而且除了forEach操作,其它的都应该在理想的情况下是没有副作用的。在Entry上的桶操作不支持方法setValue。
forEach:对每一个元素执行操作。在执行这个操作之前,一个可变的形式在每一个元素上应用一个给定的转化。
search:返回第一个非null的在每一个元素上应用了给定的函数的结果。当一个元素被发现,就会跳过进一步的搜索。
reduce:收集每一个元素,支持的减少函数不能够依赖顺序(更官方的说法是,它应该是可关联而且可交换的。)有5个变形:
Plain reductions。(没有格式为(Key,Value)作为函数参数的这个方法,因为没有相对应的返回值。)
Mapped reductions,收集作用于每一个元素的函数的结果。
Reductions使用基础值作用于标量doubles,longs,ints。
这些桶操作接受parallelismThreshold参数,如果当前的map的size被检测为小于给定的阈值,方法会连续执行。使用Long.MAX_VALUE作为并行度的上限,使用值1会造成最大的并行度,最大并行度通过将主任务分离为足够的子任务来充分利用commonPool()来实现。通常,你在开始的时候可能会选择这些极端值的一个,然后选择能够平衡开销和吞吐量的中间值。
这些桶操作中的并发属性跟随自ConcurrentHashMap中的并发属性:任意非空的get(key)的返回值、相关的取数据的方法,忍受了一个提前发生的和关联的插入或者更新的关系。任意桶操作的结果反映了每一对元素间关系的结合(但是不是必须是原子的,当考虑到map是一个整体, 除非它被认为是静止的)。相反地,因为map中的keys和values都不是null,null可以作为一个非常可靠的结果缺失的指示器。为了维持这个属性,null作为一个隐式的所有非标量减少操作的基础。对于double、long和int版本,这个基础应该是:当它和任何值结合的时候,返回那个值(它应该是减少的标识元素)。大多数的减少操作拥有
2019.10.17更新