Java中的ConcurrentHashMap是什么?

63

ConcurrentHashMap在Java中有什么用途?它有哪些好处?它是如何工作的?提供示例代码会很有帮助。


请返回翻译后的文本:有用的http://javarevisited.blogspot.in/2013/02/concurrenthashmap-in-java-example-tutorial-working.html - roottraveller
6个回答

79

这段代码的意图是提供一个线程安全的HashMap实现。多个线程可以同时读写它,而不会收到过时或损坏的数据。 ConcurrentHashMap 提供了自己的同步机制,因此您无需明确同步对其进行访问。

ConcurrentHashMap 的另一个特性是提供了putIfAbsent方法,如果指定的键不存在,则会以原子方式添加映射。请考虑以下代码:

ConcurrentHashMap<String, Integer> myMap = new ConcurrentHashMap<String, Integer>();

// some stuff

if (!myMap.contains("key")) {
  myMap.put("key", 3);
}

该代码不是线程安全的,因为在调用 contains 和调用 put 之间,另一个线程可能会为 "key" 添加映射。正确的实现应该是:

myMap.putIfAbsent("key", 3);

7
我会将其描述为“安全异步”的方式。它允许两个线程同时运行并且保证在最终状态时处于一致的状态。 - Affe
6
同步不是(外部)排序的保证。 - danben
7
请求获取监视器的线程不一定按请求顺序获得,但是一旦一个线程获取了监视器并开始修改映射表,其他线程在修改完成之前将无法看到它,这是一种 happens-before 保证。在 Concurrent Map 中,一个线程可能开始执行 put 操作,然后很长一段时间没有再次被调度,其他线程可以执行 get 操作,但不会看到正在进行的 put 操作。 - Affe
由于gets函数无法看到正在进行的puts函数,这不会导致不一致状态吗? - Vanchinathan Chandrasekaran
我注意到你使用了一个String键; 如果 string1.equals(string2)&&string1!=string2,我仍然可以在ConcurrentHashMap中获取该项吗? - Andrew Wyld

32

ConcurrentHashMap允许并发访问映射。HashTable也提供了对映射的同步访问,但需要锁住整个映射以执行任何操作。

ConcurrentHashMap的逻辑是,只有一部分[segments]被锁定,而不是锁定整个表。每个段管理其自己的HashTable。仅对更新应用锁定。在检索的情况下,它允许完全并发性。

假设有四个线程同时在容量为32的映射上工作,该表被分成四个段,每个段管理一个具有容量的哈希表。默认情况下,该集合维护16个段的列表,每个段用于保护(或锁定)映射的单个桶。

enter image description here

这实际上意味着可以同时修改16个线程的集合。使用可选的concurrencyLevel构造函数参数可以增加并发级别。

public ConcurrentHashMap(int initialCapacity,
                         float loadFactor, int concurrencyLevel)

就像其他回答所述,ConcurrentHashMap提供了新的方法putIfAbsent(),该方法类似于put,但如果键已经存在,则不会覆盖该值。

private static Map<String,String> aMap =new ConcurrentHashMap<String,String>();

if(!aMap.contains("key"))
   aMap.put("key","value");

新方法也更快,因为它避免了像上面的双重遍历contains方法必须定位段并迭代表格以查找键,再次,方法put必须遍历桶并放置键。


默认情况下大小为32,那么4个线程如何创建大小为16的段。难道不应该是8吗? - jayendra bhatt

12

真正的功能区别在于,在您使用时,如果其他人更改它,它不会抛出异常和/或损坏。

对于普通的集合,如果另一个线程在您访问(通过迭代器)时添加或删除元素,它将抛出异常。ConcurrentHashMap允许它们进行更改并且不停止您的线程。

请注意,它不提供任何同步保证或承诺线程之间变化的瞬时可见性。(它有点像读提交的数据库隔离级别,而不是行锁定的序列化数据库隔离级别。 (老派的行锁定SQL序列化,而不是Oracle风格的多版本序列化 :))

我知道的最常见的用途是在App Server环境中缓存不可变派生信息,许多线程可能正在访问相同的东西,并且如果两个线程交错计算相同的缓存值并将其放入两次,这并不重要等等。(例如,它在Spring WebMVC框架内广泛用于保存运行时派生配置,例如从URL到处理程序方法的映射。)


1
这完全不是真的。Javadocs明确指定所有操作都是线程安全的。 - danben
所有的操作都是线程安全的,但是与同步的 map 不同,它没有 happens-before 承诺。当您查看 ConcurrentHashMap 时,您所看到的是最近完成操作的结果。其中一些操作可能比您开始尝试查看时晚得多。它的工作方式有点像读提交的数据库隔离,而同步的 map 更像是可序列化的数据库隔离。 - Affe

4

它可以用于记忆化:

import java.util.concurrent.ConcurrentHashMap;
public static Function<Integer, Integer> fib = (n) -> {
  Map<Integer, Integer> cache = new ConcurrentHashMap<>();
  if (n == 0 || n == 1) return n;
  return cache.computeIfAbsent(n, (key) -> HelloWorld.fib.apply(n - 2) + HelloWorld.fib.apply(n - 1));
};

0
大家好,今天我们讨论了ConcurrentHashMap。
什么是ConcurrentHashMap?
ConcurrentHashMap是Java 1.5中引入的一个类,它实现了ConcurrentMap和Serializable接口。ConcurrentHashMap在处理多线程时增强了HashMap的性能。我们知道,当应用程序有多个线程时,HashMap不是一个好选择,因为会出现性能问题。
以下是ConcurrentHashMap的一些关键点:
  • ConcurrentHashMap的底层数据结构是HashTable。
  • ConcurrentHashMap是一个类,该类是线程安全的,这意味着多个线程可以在单个线程对象上进行访问而不会出现任何复杂情况。
  • ConcurretnHashMap对象根据并发级别分成多个段。
  • ConcurrentHashMap的默认并发级别为16。
  • 在ConcurrentHashMap中,任意数量的线程都可以执行检索操作,但对于对象的更新,线程必须锁定要操作的特定段。
  • 这种锁定机制称为段锁定或桶锁定。
  • 在ConcurrentHashMap中,同时执行16个更新操作。
  • ConcurrentHashMap中不允许插入null值。
以下是ConcurrentHashMap的构造方法。 ConcurrentHashMap m = new ConcurrentHashMap();:创建具有默认初始容量(16)、加载因子(0.75)和并发级别(16)的新空映射。 ConcurrentHashMap m = new ConcurrentHashMap(int initialCapacity);:创建具有指定初始容量和默认加载因子(0.75)和并发级别(16)的新空映射。 ConcurrentHashMap m = new ConcurrentHashMap(int initialCapacity, float loadFactor);:创建具有指定初始容量和加载因子以及默认并发级别(16)的新空映射。 ConcurrentHashMap m = new ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel);:创建具有指定初始容量、加载因子和并发级别的新空映射。 ConcurrentHashMap m = new ConcurrentHashMap(Map m);:创建一个与给定映射具有相同映射关系的新映射。 ConcurretHashMap 有一个名为 putIfAbsent() 的方法,该方法防止存储重复的键,请参考下面的示例。
    import java.util.concurrent.*; 

     class ConcurrentHashMapDemo { 
     public static void main(String[] args) 
     { 
         ConcurrentHashMap m = new ConcurrentHashMap(); 
          m.put(1, "Hello"); 
          m.put(2, "Vala"); 
          m.put(3, "Sarakar"); 

         // Here we cant add Hello because 1 key 
         // is already present in ConcurrentHashMap object 

            m.putIfAbsent(1, "Hello"); 

         // We can remove entry because 2 key 
         // is associated with For value 

            m.remove(2, "Vala"); 

        // Now we can add Vala

            m.putIfAbsent(4, "Vala"); 


            System.out.println(m); 
      } 
}  

0

1.ConcurrentHashMap是线程安全的,这意味着代码一次只能被单个线程访问。

2.ConcurrentHashMap在Map的特定部分上进行同步或锁定。为了优化ConcurrentHashMap的性能,Map根据并发级别划分成不同的分区。这样我们就不需要同步整个Map对象。

3.默认的并发级别是16,因此Map被划分为16个部分,每个部分由不同的锁控制,这意味着可以有16个线程操作。

4.ConcurrentHashMap不允许NULL值。因此,在ConcurrentHashMap中,键不能为null。


点1需要注意。 - Dave Richardson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接