ConcurrentMap是Java集合框架的一个接口,用于在Java中创建一个线程安全的Map。它将对象作为键和值对存储到Map中,但以一种同步的方式。
尽管我们在Java中已经有了HashMap和HashTable,但这些都不能在并发环境中很好地工作。因此,建议在线程安全的应用程序中使用并发地图。
1.Java ConcurrentMap是如何工作的?
在内部,ConcurrentMap使用数据段(分区或 分区),将地图内部划分为这个数量的分区(默认为16)。当一个线程进行添加或更新操作时,ConcurrentMap会锁定必须进行更新的那个特定段。但它允许其他线程从其他未锁定的段中读取任何值。
这意味着我们在多线程应用中访问ConcurrentMap时不需要有同步块,因为数据的一致性是在内部保持的。

在正常的应用中,一个分片可以存储合理数量的键值对,并允许多线程进行读取操作。而且读取性能也非常理想。当有太多的碰撞时,表会被动态地扩展。
注意,方法size()、isEmpty()和containsValue()的结果反映了map的瞬时状态,通常对监控或估计有帮助,但对程序控制没有帮助。
2.2.ConcurrentMap的实现
以下是用Java实现ConcurrentMap的类。
2.1.并发哈希图(ConcurrentHashMap
ConcurrentHashMap是ConcurrentMap的一个实现类,与*HashTable*类似,只是它将数据存储到小的内存段中,使其可以独立地用于并发线程。
默认情况下,它创建了16个可以被并发线程访问的段,并为修改记录而被锁定。它使用 发生-之前概念来更新记录。它不对读取操作进行任何锁定,并向线程提供最新的更新数据。
2.2.ConcurrentSkipListMap
它是ConcurrentMap和ConcurrentNavigableMap的一个实现类。它以自然排序的方式或者按照比较器在初始化时指定的方式存储数据。它的实现基于SkipLists数据结构,其插入、删除和搜索操作的整体复杂度为O(log n) 。
另外,请注意ConcurrentHashMap中的键不是按排序的,所以对于需要排序的情况**,ConcurrentSkipListMap**是一个更好的选择。它是TreeMap的一个并发版本。它默认以升序排列键值。
3.ConcurrentMap操作
让我们学习如何对并发地图进行各种操作。
3.1.创建一个并发地图
要使用一个并发地图,我们可以使用创建其任何一个实现类的实例。我们可以调用构造函数并传递所需的参数,如初始容量、负载因子和并发级别。
- 默认构造函数将创建一个空的ConcurrentMap,其值为,负载因子为。 初始容量的
16,负载因子为0.75f。 - 该 负载因子控制地图内部的密集包装,进一步优化内存使用。
- 该 并发级别控制地图中分片的数量。例如,并发级别设置为1将确保只创建和维护一个分片。
注意,这些参数只影响地图的初始大小。在地图调整大小的过程中,它们可能不会被考虑。
ConcurrentMap<Integer, String> cmap = new ConcurrentHashMap<>();
ConcurrentHashMap(int initialCapacity);
ConcurrentHashMap(int initialCapacity, float loadFactor);
ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel);
将现有的地图传递给它的构造函数,以初始化一个与给定地图条目相同的ConcurrentMap。
ConcurrentHashMap(Map<? extends K,? extends V> m)
3.2.添加条目
要向一个并发地图添加元素,我们可以使用下面的方法之一。
put(key, value):需要两个参数,第一个参数是键,第二个参数是值。键和值都不可以是空的。putIfAbsent(key, value):如果指定的键没有与一个值相关联(或者被映射为null),则将其与给定的值相关联并返回null,否则返回当前值。computeIfAbsent(key, mappingFunction):如果指定的键还没有与一个值相关联,它将尝试使用给定的映射函数来计算该值,并将其输入到地图中,除非为空。当计算值是一个昂贵的操作时,例如从远程系统或数据库中获取值,这个方法是非常有益的。这种方法确保只有当值不在地图上时才会进行计算,从而防止不必要的计算。
对于计算...和合并...操作,如果计算的值是空的,那么键值映射如果存在就会被移除,如果以前不存在就会保持不存在。
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-loop或增强的for-loop。
ConcurrentMap<Integer, String> cmap = new ConcurrentHashMap<>();
cmap.put(1, "Delhi");
cmap.put(2, "NewYork");
cmap.put(3, "London");
// Iterating concurrent map keys
for (Integer entry : cmap.keySet()) {
System.out.println("Entry -- " + entry);
}
// Iterating concurrent map values
for (String value : cmap.values()) {
System.out.println("Value -- " + value);
}
// Iterating concurrent map entries
for (Map.Entry<Integer, String> entry : cmap.entrySet()) {
System.out.println(entry.getKey() + " -- " + entry.getValue());
}
ConcurrentMap也支持流操作。在流的批量操作中,与上述迭代器类似,它不会抛出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.处理并发Map中缺失的键
Java在其1.8版本中增加了一个新方法 使用getOrDefault()函数在其1.8版本中增加了一个新的方法来处理丢失的键。如果指定的键在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); //Prints Delhi
val = cmap.getOrDefault(10, "Kolkata");
System.out.println("Value = "+val); //Prints Kolkata
5.总结
ConcurrentMap及其实现很适合高并发的应用。在本教程中,我们学习了初始构造函数的参数,这些参数在并发操作中改变了地图实例的行为。
我们还学习了在地图上执行各种操作的例子和最佳实践,以获得最佳性能。