爪哇2博客
爪哇2博客

爪哇中的ConcurrentHashMap

爪哇中的ConcurrentHashMap
并发哈希图在Java 5中与其他并发utils一起引入,例如 CountDownLatch, 循环屏障阻塞队列.
爪哇中的ConcurrentHashMap与HashTable非常相似,但是它提供了更好的并发级别。
您可能知道,您可以同步 哈希图 使用Collections.synchronizedMap(Map)。因此,ConcurrentHashMap和Collections.synchronizedMap(Map)之间有什么区别?对于Collections.synchronizedMap(Map),它锁定整个HashTable对象,但是在ConcurrentHashMap中,它仅锁定一部分对象。您将在以后的部分中了解它。
另一个区别是,如果我们尝试在迭代时修改ConcurrentHashMap,则ConcurrentHashMap不会引发ConcurrentModification异常。

让’举一个非常简单的例子。我有一个Country类,我们将使用Country类对象作为键,并使用其大写名称(字符串)作为值。下面的示例将帮助您了解如何将这些键值对存储在 并发哈希图.

爪哇示例中的ConcurrentHashMap:

1. 国家.java 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 
组织.Arpit.爪哇2blog;
上市 国家 {
 
名称;
人口;
 
上市 国家( 名称, 人口) {
  ();
  这个.名称 = 名称;
  这个.人口 = 人口;
}
上市 得到Name() {
  返回 名称;
}
上市 虚空 setName( 名称) {
  这个.名称 = 名称;
}
上市 得到Population() {
  返回 人口;
}
上市 虚空 setPopulation( 人口) {
  这个.人口 = 人口;
}
@覆写
上市 整型 杂凑Code() {
最后 整型 主要 = 31;
整型 结果 = 1;
结果 = 主要 * 结果 + ((名称 == 空值) ? 0 : 名称.杂凑Code());
返回 结果;
}
@覆写
上市 布尔值 等于(目的 对象) {
 
  国家 其他 = (国家) 对象;
   如果 (名称.等于IgnoreCase((其他.名称)))
   返回 真正;
  返回 ;
}
 
}
 

2. 并发哈希图Structure.java(主班)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 
进口 爪哇.实用程序.哈希图;
进口 爪哇.实用程序.迭代器;
 
上市 并发哈希图Structure {
 
    / **
    * @作者Arpit Mandliya
     * /
    上市 静态的 虚空 主要([] args) {
 
        国家 印度= 国家(“印度”,1000);
        国家 日本= 国家(“日本”,10000);
 
        国家 法国= 国家(“法国”,2000);
        国家 俄国= 国家(“俄国”,20000);
 
        并发哈希图<国家,> 国家CapitalMap= 并发哈希图<国家,>();
        国家CapitalMap.(印度,“德里”);
        国家CapitalMap.(日本,“东京”);
        国家CapitalMap.(法国,“巴黎”);
        国家CapitalMap.(俄国,“莫斯科”);
 
        迭代器 国家CapitalIter=国家CapitalMap.键Set().迭代器();//将调试点放在这一行
        (国家CapitalIter.hasNext())
        {
            国家 国家Obj=国家CapitalIter.下一页();
             首都=国家CapitalMap.得到(国家Obj);
            系统..打印(国家Obj.得到Name()+“ ----”+首都);
            }
        }
 
}
 

现在,在第23行放置调试点,然后右键单击项目->debug as->Java应用程序。程序将在第23行停止执行,然后右键单击countryCapitalMap,然后选择watch。您将看到如下结构。
现在,从上图可以观察到以下几点
    1. 有一个称为segments的Segment []数组,其大小为16。
    2. 它还有两个变量,分别是segmentShift和segmentMask。
    3. 此细分存储细分类别’的对象。 并发哈希图类具有一个称为Segment的内部类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
/ **
    *段是哈希表的专用版本。  This
    * 重入锁的子类是偶然的,只是
    *简化一些锁定,避免单独构造。
     * /
静态的 最后 分割<K,V> 延伸 重入锁 实施 可序列化 {
/ **
      *每个细分表。
     */
        短暂的 易挥发的 哈希输入<K,V>[] ;
//其他方法和变量
}
 

现在让我们展开位于索引3的线段对象。

在上图中,您可以看到每个Segment类在逻辑上都包含一个HashMap。
这是table [](HashEntry类的数组)的大小:2ˆk>=(容量/段数)
它在名为HashEntry的类中存储一个键值对,该类类似于HashMap中的Entry类。

1
2
3
4
5
6
7
8
 
静态的 最后 哈希输入<K,V> {
        最后 K ;
        最后 整型 杂凑;
        易挥发的 V ;
        最后 哈希输入<K,V> 下一页;
}
 

当我们说时,ConcurrentHashMap仅锁定它的一部分,实际上它锁定了Segment。因此,如果两个线程在同一ConcurrentHashMap中写入不同的段,则它允许写入操作而不会发生任何冲突。

因此,细分仅用于写操作。在读取操作的情况下,它允许完全并发,并使用易失性变量提供最新更新的值。
现在,当您了解ConcurrentHashMap的内部结构时,将更容易理解put函数。

并发级别的概念:

创建ConcurrentHashMap对象时,可以在构造函数中传递ConcurrencyLevel。 ConcurrencyLevel定义”估计要写入ConcurrentHashMap的线程数”。默认ConcurrencyLevel为16。这就是为什么在上面创建的ConcurrentHashMap中获得16个Segments对象的原因。
段的实际数量将等于ConcurrencyLevel中定义的2的下一个幂。
例如:
让s say you have defined ConcurrencyLevel as 5, so 8 分割s 对象ect will be created  as 8=2^3 so 3 higher bits of 键will be used to find 指数 of the segment
另一个例子: 您希望10个线程应该能够同时访问ConcurrentHashMap,所以您将ConcurrencyLevel定义为10,因此将创建16个Segments为16 = 2 ^ 4,因此将使用Key的4个高位查找该分段的索引

输入:

放Entry的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
 
/ **
    *将指定的键映射到此表中的指定值。
     * Neither the 键 nor the 值 can be 空值.
     *
    *可以通过调用<tt>get</tt> method
     * with a 键 that is equal to the original 键.
     *
    * @param 键与指定值关联的键
    * @param与指定键关联的值
    * @返回与之关联的先前值<tt>key</tt>, or
     *         <tt>null</tt>如果没有映射<tt>key</tt>
    * @throws 空指针异常如果指定的键或值是null
     * /
    上市 V (K , V ) {
        如果 ( == 空值)
             空指针异常();
        整型 杂凑 = 杂凑(.杂凑Code());
        返回 segmentFor(杂凑).(, 杂凑, , );
    }
 
/ **
    *返回应用于给定哈希值的键的段
     * @param 杂凑 the 杂凑 code for the 键
    * @返回段
     * /
    最后 分割 segmentFor(整型 杂凑) {
        返回 [(杂凑 >>> segmentShift) & segmentMask];
    }
//将方法放入细分中:
V (K , 整型 杂凑, V , 布尔值 onlyIfAbsent) {
            ();
            尝试 {
                整型 c = 计数;
                如果 (c++ > ) //确保容量
                    重新整理();
                哈希输入[] 标签 = ;
                整型 指数 = 杂凑 & (标签.长度 - 1);
                哈希输入 第一 = 标签[指数];
                哈希输入 e = 第一;
                 (e != 空值 && (e.杂凑 != 杂凑 || !.等于(e.)))
                    e = e.下一页;
 
                V 旧值;
                如果 (e != 空值) {
                    旧值 = e.;
                    如果 (!onlyIfAbsent)
                        e. = ;
                }
                其他 {
                    旧值 = 空值;
                    ++modCount;
                    标签[指数] = 哈希输入(, 杂凑, 第一, );
                    计数 = c; //写易失性
                }
                返回 旧值;
            } 最后 {
                开锁();
            }
        }
 

当我们将任何键值对添加到ConcurrentHashMap时,将执行以下步骤:

  1. 在ConcurrentHashMap中,键不能为null。键’的hashCode方法用于计算哈希码
  2. 键‘的HashCode方法可能写得不好,因此Java开发人员又添加了一个方法hash(类似于HashMap),应用了另一个hash()函数并计算了hashcode。
  3. 现在我们需要首先找到线段的索引,要使用给定的键查找线段,可以使用上面的SegmentFor方法。
  4.  获得细分后,我们使用细分’put方法。将键值对放入Segment时,它获取锁,因此没有其他线程可以进入此块,然后使用哈希在HashEntry数组中找到索引&(tab.length-1).
  5. 如果您仔细观察,则细分’的put方法类似于HashMap’s 放 method.

放IfAbsent:

You want to 放 element in 并发哈希图 only when 如果 it does not have 键already 其他wise 返回 old 值. 这个 can be written as:
1
2
3
4
5
6
7
 
如果 (地图.得到()==空值)
 
    返回 地图.(, );
其他
   返回 地图.得到();
 
Above operation is atomic 如果 you use 放IfAbsent method. 这个 may be required behaviour. 让’借助示例了解:
  1. 线程1将值放入ConcurrentHashMap中。
  2. 同时,线程2试图从ConcurrentHashMap中读取(获取)值,并且可能返回null,因此它可能会覆盖线程1放入ConcurrentHashMap中的内容。

可能不需要上述行为,因此ConcurrentHashMap具有putIfAbsent方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 
    / *
 
     * {@inheritDoc}
     *
 
     * @返回与指定键关联的先前值,
 
     *         or <tt>null</tt>如果没有映射the 键
 
     * @throws 空指针异常如果指定的键或值是null
     */
 
    上市 V 放IfAbsent(K , V ) {
        如果 ( == 空值)
            空指针异常();
        整型 杂凑 = 杂凑(.杂凑Code());
        返回 segmentFor(杂凑).(, 杂凑, , 真正);
 
    }
 

得到 Entry:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
 
 / **
 
     * Returns the 值 to which the specified 键 is 地图ped,
     *或{@code 空值}(如果此映射不包含键的映射)。
     *
     *
More formally, 如果 这个 地图 contains a 地图ping from a 键
*将{@code k}设置为值{@code v},以使{@code 键.equals(k)},
*然后此方法返回{@code v};否则返回
* {@code 空值}.  (There can be at most one such 地图ping.)
*
* @throws 空指针异常 如果 the specified 键 is 空值
* /
上市 V 得到(目的 ) {
整型 杂凑 = 杂凑(.杂凑Code());
返回 segmentFor(杂凑).得到(, 杂凑);
}
 
/ *地图方法的专门实现* /
 
// 得到 method in 分割:
V 得到(目的 , 整型 杂凑) {
如果 (计数 != 0) { //易读
哈希输入<K,V> e = 得到First(杂凑);
(e != 空值) {
如果 (e.杂凑 == 杂凑 && .等于(e.)) {
V v = e.;
如果 (v != 空值)
返回 v;
返回 readValueUnderLock(e); //重新检查
}
e = e.下一页;
}
}
返回 空值;
}
 

从ConcurrentHashMap获得价值是直截了当的。

  1. Calculate 杂凑 using 键 ‘s Hashcode
  2. Use segmentFor to 得到 指数 of 分割.
  3. 使用区隔’s 得到函数获取对应于键的值。
  4. 如果在ConcurrentHashMap中找不到值,则它将锁定细分并再次尝试获取值。

最佳实践:

如果我们需要低级别的并发级别,则不应使用默认的ConcurrentHashMap构造函数,因为默认的ConcurrencyLevel为16,并且默认情况下它将创建16个Segments。
我们应该使用完全参数化的构造函数:
并发哈希图(int initialCapacity,floatloadFactor,intconcurrencyLevel) 
在上面的构造函数中,initialCapacity和loadFactor与HashMap相同,concurrencyLevel与我们上面定义的相同。
因此,如果仅需要两个可以同时写入的线程,则可以将ConcurrentHashMap初始化为:
1
2
3
 
 并发哈希图 ch= 并发哈希图(16,0.75f,2);
 

如果您的写入线程很少而读取线程很多,那么ConcurrentHashMap的性能会更好。

那’Java中的所有aboutConcurrentHashMap。

请通过  经验丰富的Java核心面试问题 了解更多面试问题。


导入联系人

您可能还喜欢:

分享这个

作者

关注作者

相关文章

Comments

发表评论

您的电子邮件地址不会被公开。 必需的地方已做标记 *

订阅我们的新闻

获取质量教程到您的收件箱。现在订阅。


让’s be Friends

©2020 爪哇2博客