Java ConcurrentMap详解指南

培训教学 潘老师 7个月前 (10-12) 174 ℃ (0) 扫码查看

在本教程中,我们将详细了解Java集合框架中的ConcurrentMap接口以及ConcurrentMap与HashMap之间的区别。ConcurrentMap用于在Java中创建线程安全的Map,以同步方式存储对象作为键值对。

尽管在Java中已经有HashMap和HashTable,但在并发上下文中,它们都不适用得很好。因此,在线程安全的应用程序中推荐使用并发映射。

1.Java ConcurrentMap是如何工作的?

在内部,ConcurrentMap使用数据段(片段或分区)将Map分为一定数量的分区(默认为16)。当线程执行添加或更新操作时,ConcurrentMap锁定要进行更新的特定分区。但它允许其他线程从其他未锁定的分区读取任何值。

这意味着在多线程应用程序中访问ConcurrentMap时,我们不需要使用同步块,因为数据一致性在内部得到维护。在正常应用程序中,单个分区存储合理数量的键值对,并允许多个线程执行读操作。并且读取性能也非常理想。当碰撞太多时,表会动态扩展。

请注意,size()、isEmpty()和containsValue()方法的结果反映了Map的瞬时状态,通常用于监控或估算,而不用于程序控制。

2.ConcurrentMap实现

以下类实现了Java中的ConcurrentMap。

2.1. ConcurrentHashMap

ConcurrentHashMap是ConcurrentMap的实现类,类似于HashTable,不同之处在于它将数据存储在小的内存段中,以便供并发线程独立访问。

默认情况下,它创建16个可以由并发线程访问并为修改记录而锁定的分段。它使用happens-before概念来更新记录。对于读取操作,它不执行任何锁定,并为线程提供最新的更新数据。

2.2. ConcurrentSkipListMap

它是ConcurrentMap和ConcurrentNavigableMap的实现类。它按照自然排序顺序或在初始化时由比较器指定的顺序存储数据。它的实现基于SkipLists数据结构,对于插入、删除和搜索操作具有整体O(log n)复杂度。

还要注意,ConcurrentHashMap中的键不是按排序顺序排列的,因此对于需要排序的情况,ConcurrentSkipListMap是更好的选择。它是TreeMap的并发版本,默认按升序对键进行排序。

3.ConcurrentMap操作

让我们学习如何在ConcurrentMap上执行各种操作。

3.1. 创建ConcurrentMap

要使用ConcurrentMap,我们可以创建其实现类的实例。我们可以调用构造函数并传递所需的参数,如初始容量、负载因子和并发级别。

  • 默认构造函数将创建一个初始容量为16和负载因子为0.75f的空ConcurrentMap。
  • 负载因子控制Map内部的密集打包,进一步优化内存使用。
  • 并发级别控制Map中分区的数量。例如,将并发级别设置为1将确保只创建和维护一个分区。

请注意,这些参数只影响Map的初始大小,可能在Map调整大小时不会考虑。

ConcurrentMap<Integer, String> cmap = new ConcurrentHashMap<>();
ConcurrentHashMap(int initialCapacity);
ConcurrentHashMap(int initialCapacity, float loadFactor);
ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel);

通过将现有Map传递给其构造函数,可以初始化具有与给定Map相同条目的ConcurrentMap。

ConcurrentHashMap(Map<? extends K,? extends V> m)

3.2. 添加条目

要向ConcurrentMap中添加元素,我们可以使用以下其中一种方法:

  • put(key, value):接受两个参数,第一个参数是键,第二个是值。键和值都不能为null。
  • putIfAbsent(key, value):如果指定键尚未与值关联(或映射到null),则将其与给定值关联并返回null,否则返回当前值。
  • computeIfAbsent(key, mappingFunction):如果指定键尚未与值关联,它尝试使用给定的映射函数计算值,并将其输入Map,除非为null。当计算值是昂贵的操作(例如从远程系统或数据库获取值)时,此方法非常有用。此方法确保仅在Map上不存在值时才会发生计算,从而防止不必要的计算。

对于compute…和merge…操作,如果计算值为null,则如果存在则删除键值映射,或者如果之前不存在则保持不变。

ConcurrentMap<Integer, String> cmap = new ConcurrentHashMap<>();
cmap.put(1, "Delhi");
cmap.putIfAbsent(2, "NewYork");
cmap.computeIfAbsent("3", k -> getValueFromDatabase(k));

3.3. 删除条目

使用remove()方法通过其键来删除条目。

cmap.remove(2);

3.4. 遍历条目

要遍历ConcurrentMap的键、值或条目,可以使用简单的for循环或增强的for循环。

ConcurrentMap<Integer, String> cmap = new ConcurrentHashMap<>();
cmap.put(1, "Delhi");
cmap.put(2, "NewYork");
cmap.put(3, "London");
// 迭代concurrent map keys
for (Integer entry : cmap.keySet()) {
  System.out.println("Entry -- " + entry);
}
// 迭代concurrent map values
for (String value : cmap.values()) {
  System.out.println("Value -- " + value);
}
// 迭代concurrent map entries
for (Map.Entry<Integer, String> entry : cmap.entrySet()) {
  System.out.println(entry.getKey() + " -- " + entry.getValue());
}

ConcurrentMap还支持流操作。在Stream中的批量操作中,与上述迭代器类似,它不会抛出ConcurrentModificationException。

Stream.of(cmap.entrySet()).forEach(System.out::println);

3.5. 将HashMap转换为ConcurrentMap

要将HashMap转换为ConcurrentMap,使用其构造函数并将hashmap作为构造函数参数。

Map<Integer, String> hashmap = ...;
ConcurrentMap<Integer, String> cmap = new ConcurrentHashMap<>(hashmap);

4.处理ConcurrentMap中的缺失键

Java在其1.8版本中添加了一个新方法getOrDefault()来处理缺失的键。如果指定键在ConcurrentMap中不存在,此方法将返回默认值。

ConcurrentMap<Integer, String> cmap = new ConcurrentHashMap<>();
cmap.put(1, "Delhi");
cmap.put(2, "NewYork");
cmap.put(3, "London");
String val = cmap.getOrDefault(1,"Bombay");
System.out.println("Value = "+val);       //打印 Delhi
val = cmap.getOrDefault(10, "Kolkata");
System.out.println("Value = "+val);       //打印 Kolkata

5.结论

ConcurrentMap及其实现适用于高度并发的应用程序。在本教程中,我们学习了在并发操作期间更改映射实例行为的初始构造函数参数。

我们还学习了如何执行映射的各种操作,使用示例以及实现最佳性能的最佳实践。


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/teach/9479.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】