————————————
————————————
In the first two issues, we explained the basic principles of HashMap and the problems in high concurrency scenarios. For those who haven't seen it, please click the link below:
It doesn't matter if you are too lazy to read it, let's briefly review the structure of HashMap:
In simple terms, a HashMap is an array of Entry objects. Each Entry element in the array is also the head node of a linked list.
Hashmap is not thread safe. When inserting operations in a high concurrency environment, the following circular linked list may appear:
What is Segment? Segment itself is equivalent to a HashMap object.
Like HashMap, Segment contains an array of HashEntry, and each HashEntry in the array is both a key-value pair and the head node of a linked list.
A single Segment structure is as follows:
How many Segment objects like this are in the ConcurrentHashMap collection? There are 2 to the Nth power, which are stored together in an array called segments.
Therefore, the structure of the entire ConcurrentHashMap is as follows:
It can be said that ConcurrentHashMap is a secondary hash table. Below a general hash table, there are several sub-hash tables.
Such a secondary structure is somewhat similar to the horizontal split of a database.
Case1: Concurrent writing of different segments
Writing to different segments can be executed concurrently.
Case2: One write and one read of the same segment
Writing and reading of the same segment can be executed concurrently.
Case3: Concurrent writes to the same segment
Segment writes need to be locked, so concurrent writes to the same segment will be blocked.
It can be seen that each segment in the ConcurrentHashMap holds a lock. While ensuring thread safety, the granularity of the lock is reduced, making concurrent operations more efficient.
Get method:
1. Do Hash operation for the input Key to get the hash value.
2. Through the hash value, locate the corresponding Segment object
3.再次通过hash值,定位到Segment当中数组的具体位置。
Put方法:
1.为输入的Key做Hash运算,得到hash值。
2.通过hash值,定位到对应的Segment对象
3.获取可重入锁
4.再次通过hash值,定位到Segment当中数组的具体位置。
5.插入或覆盖HashEntry对象。
6.释放锁。
Size方法的目的是统计ConcurrentHashMap的总元素数量, 自然需要把各个Segment内部的元素数量汇总起来。
但是,如果在统计Segment元素数量的过程中,已统计过的Segment瞬间插入新的元素,这时候该怎么办呢?
ConcurrentHashMap的Size方法是一个嵌套循环,大体逻辑如下:
1.遍历所有的Segment。
2.把Segment的元素数量累加起来。
3.把Segment的修改次数累加起来。
4.判断所有Segment的总修改次数是否大于上一次的总修改次数。如果大于,说明统计过程中有修改,重新统计,尝试次数+1;如果不是。说明没有修改,统计结束。
5.如果尝试次数超过阈值,则对每一个Segment加锁,再重新统计。
6.再次判断所有Segment的总修改次数是否大于上一次的总修改次数。由于已经加锁,次数一定和上次相等。
7.释放锁,统计结束。
为什么这样设计呢?这种思想和乐观锁悲观锁的思想如出一辙。
为了尽量不锁住所有Segment,首先乐观地假设Size过程中不会有修改。当尝试一定次数,才无奈转为悲观锁,锁住所有Segment保证强一致性。
几点说明:
1. 这里介绍的ConcurrentHashMap原理和代码,都是基于Java1.7的。在Java8中会有些许差别。
2.ConcurrentHashMap在对Key求Hash值的时候,为了实现Segment均匀分布,进行了两次Hash。有兴趣的朋友可以研究一下源代码。
转载自:http://www.sohu.com/a/205451532_684445