ConcurrentHashMap需要知道的细节

1、public V get(Object key)不涉及到锁,也就是说获得对象时没有使用锁;
2、keySet().iterator()及keys(),获取的Iterator、Enumeration变量是单线程访问安全的,多线程访问时要么生成多个Iterator、Enumeration(通过调用相应的获取方法),要么以ConcurrentHashMap变量为锁进行同步(synchronized该变量);ConcurrentHashMap变量是多线程访问安全的,尽管是多线程访问,多数情况下应该没有锁争用;
3、put、remove方法要使用锁,但并不一定有锁争用,原因在于ConcurrentHashMap将缓存的变量分到多个Segment,每个Segment上有一个锁,只要多个线程访问的不是一个Segment就没有锁争用,就没有堵塞,各线程用各自的锁,ConcurrentHashMap缺省情况下生成16个Segment,也就是允许16个线程并发的更新而尽量没有锁争用;
4、Iterator、Enumeration获得的对象,不一定是和其它更新线程同步,获得的对象可能是更新前的对象,ConcurrentHashMap允许一边更新、一边遍历,未遍历到的key一般能放映value更新;
5、有些情况下这种不一致是允许的,如果需要最大的性能、吞吐量,则正好使用ConcurrentHashMap。

ConcurrentHashMap的简要总结:

1、public V get(Object key)不涉及到锁,也就是说获得对象时没有使用锁;

2、put、remove方法要使用锁,但并不一定有锁争用,原因在于ConcurrentHashMap将缓存的变量分到多个Segment,每个Segment上有一个锁,只要多个线程访问的不是一个Segment就没有锁争用,就没有堵塞,各线程用各自的锁,ConcurrentHashMap缺省情况下生成16个Segment,也就是允许16个线程并发的更新而尽量没有锁争用;

3、Iterator对象的使用,不一定是和其它更新线程同步,获得的对象可能是更新前的对象,ConcurrentHashMap允许一边更新、一边遍历,也就是说在Iterator对象遍历的时候,ConcurrentHashMap也可以进行remove,put操作,且遍历的数据会随着remove,put操作产出变化,所以希望遍历到当前全部数据的话,要么以ConcurrentHashMap变量为锁进行同步(synchronized该变量),要么使用CopiedIterator包装iterator,使其拷贝当前集合的全部数据,但是这样生成的iterator不可以进行remove操作。

Hashtable和ConcurrentHashMap的不同点:

1、Hashtable对get,put,remove都使用了同步操作,它的同步级别是正对Hashtable来进行同步的,也就是说如果有线程正在遍历集合,其他的线程就暂时不能使用该集合了,这样无疑就很容易对性能和吞吐量造成影响,从而形成单点。而ConcurrentHashMap则不同,它只对put,remove操作使用了同步操作,get操作并不影响,详情请看以上第1,2点,当前ConcurrentHashMap这样的做法对一些线程要求很严格的程序来说,还是有所欠缺的,对应这样的程序来说,如果不考虑性能和吞吐量问题的话,个人觉得使用Hashtable还是比较合适的;

2、Hashtable在使用iterator遍历的时候,如果其他线程,包括本线程对Hashtable进行了put,remove等更新操作的话,就会抛出ConcurrentModificationException异常,但如果使用ConcurrentHashMap的话,就不用考虑这方面的问题了,详情请看以上第3点;

在开始之前,先介绍下Map是什么?

javadoc中对Map的解释如下:

An object that maps keys to values . A map cannot contain duplicate keys; each key can map to at most one value.

This interface takes the place of the Dictionary class, which was a totally abstract class rather than an interface.

The Map interface provides three collection views, which allow a map‘s contents to be viewed as a set of keys, collection of values, or set of key-value mappings.

从上可知,Map用于存储“key-value”元素对,它将一个key映射到一个而且只能是唯一的一个value。

Map可以使用多种实现方式,HashMap的实现采用的是hash表;而TreeMap采用的是红黑树。

1. Hashtable 和 HashMap

这两个类主要有以下几方面的不同:

Hashtable和HashMap都实现了Map接口,但是Hashtable的实现是基于Dictionary抽象类。

在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。 当get()方法返回null值时,即可以表示 HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断。而在Hashtable中,无论是key还是value都不能为null 。

这两个类最大的不同在于Hashtable是线程安全的,它的方法是同步了的,可以直接用在多线程环境中。而HashMap则不是线程安全的。在多线程环境中,需要手动实现同步机制。因此,在Collections类中提供了一个方法返回一个同步版本的HashMap用于多线程的环境:

Java代码

  1. public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
  2. return new SynchronizedMap<K,V>(m);
  3. }

该方法返回的是一个SynchronizedMap 的实例。SynchronizedMap类是定义在Collections中的一个静态内部类。它实现了Map接口,并对其中的每一个方法实现,通过synchronized 关键字进行了同步控制。

2. 潜在的线程安全问题

上面提到Collections为HashMap提供了一个并发版本SynchronizedMap。这个版本中的方法都进行了同步,但是这并不等于这个类就一定是线程安全的。在某些时候会出现一些意想不到的结果。

如下面这段代码:

Java代码

  1. // shm是SynchronizedMap的一个实例
  2. if(shm.containsKey(‘key‘)){
  3. shm.remove(key);
  4. }

这段代码用于从map中删除一个元素之前判断是否存在这个元素。这里的containsKey和reomve方法都是同步的,但是整段代码却不是。考虑这么一个使用场景:线程A执行了containsKey方法返回true,准备执行remove操作;这时另一个线程B开始执行,同样执行了containsKey方法返回true,并接着执行了remove操作;然后线程A接着执行remove操作时发现此时已经没有这个元素了。要保证这段代码按我们的意愿工作,一个办法就是对这段代码进行同步控制,但是这么做付出的代价太大。

在进行迭代时这个问题更改明显。Map集合共提供了三种方式来分别返回键、值、键值对的集合:

Java代码

  1. Set<K> keySet();
  2. Collection<V> values();
  3. Set<Map.Entry<K,V>> entrySet();

在这三个方法的基础上,我们一般通过如下方式访问Map的元素:

Java代码

  1. Iterator keys = map.keySet().iterator();
  2. while(keys.hasNext()){
  3. map.get(keys.next());
  4. }

在这里,有一个地方需要注意的是:得到的keySet和迭代器都是Map中元素的一个“视图”,而不是“副本” 。问题也就出现在这里,当一个线程正在迭代Map中的元素时,另一个线程可能正在修改其中的元素。此时,在迭代元素时就可能会抛出 ConcurrentModificationException异常。为了解决这个问题通常有两种方法,一是直接返回元素的副本,而不是视图。这个可以通过

集合类的 toArray() 方法实现,但是创建副本的方式效率比之前有所降低,特别是在元素很多的情况下;另一种方法就是在迭代的时候锁住整个集合,这样的话效率就更低了。

 

3. 更好的选择:ConcurrentHashMap

java5中新增了ConcurrentMap接口和它的一个实现类ConcurrentHashMap。ConcurrentHashMap提供了和Hashtable以及SynchronizedMap中所不同的锁机制。Hashtable中采用的锁机制是一次锁住整个hash表,从而同一时刻只能由一个线程对其进行操作;而ConcurrentHashMap中则是一次锁住一个桶。ConcurrentHashMap默认将hash表分为16个桶,诸如get,put,remove等常用操作只锁当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。

上面说到的16个线程指的是写线程,而读操作大部分时候都不需要用到锁。只有在size等操作时才需要锁住整个hash表。

在迭代方面,ConcurrentHashMap使用了一种不同的迭代方式。在这种迭代方式中,当iterator被创建后集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数据 ,iterator完成后再将头指针替换为新的数据 ,这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变。

时间: 2024-10-26 02:17:45

ConcurrentHashMap需要知道的细节的相关文章

(转载)ConcurrentHashMap 原理

集合是编程中最常用的数据结构.而谈到并发,几乎总是离不开集合这类高级数据结构的支持.比如两个线程需要同时访问一个中间临界区 (Queue),比如常会用缓存作为外部文件的副本(HashMap).这篇文章主要分析jdk1.5的3种并发集合类型 (concurrent,copyonright,queue)中的ConcurrentHashMap,让我们从原理上细致的了解它们,能够让我们在深 度项目开发中获益非浅. 在tiger之前,我们使用得最多的数据结构之一就是HashMap和Hashtable.大家

ConcurrentHashMap和 CopyOnWriteArrayList提供线程安全性和可伸缩性 以及 同步的集合类 Hashtable 和 Vector Collections.synchronizedMap 和 Collections.synchronizedList 区别缺点

ConcurrentHashMap和 CopyOnWriteArrayList提供线程安全性和可伸缩性 DougLea的 util.concurrent 包除了包含许多其他有用的并发构造块之外,还包含了一些主要集合类型 List 和 Map 的高性能的.线程安全的实现.在本月的 Java理论与实践中,BrianGoetz向您展示了用 ConcurrentHashMap 替换 Hashtable 或 synchronizedMap ,将有多少并发程序获益. 在Java类库中出现的第一个关联的集合类

专用于高并发的map类-----Map的并发处理(ConcurrentHashMap)

oncurrentModificationException 在这种迭代方式中,当iterator被创建后集合再发生改变就不再是抛出ConcurrentModificationException, 取而代之的是在改变时new新的数据从而不影响原有的数据 ,iterator完成后再将头指针替换为新的数据 ,这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变. ConcurrentHashMap 原理: 集合是编程中最常用的数据结构.而谈到并发,几乎总是离不开集合这类高级数据

并发-ConcurrentHashMap

ConcurrentHashMap是Java并发包中提供的一个线程安全且高效的HashMap实现(若对HashMap的实现原理还不甚了解,可参考我的另一篇文章HashMap实现原理及源码分析),ConcurrentHashMap在并发编程的场景中使用频率非常之高,本文就来分析下ConcurrentHashMap的实现原理,并对其实现原理进行分析(JDK1.7). ConcurrentHashMap实现原理 众所周知,哈希表是中非常高效,复杂度为O(1)的数据结构,在Java开发中,我们最常见到最

java中的集合类-并发

在Java类库中出现的第一个关联的集合类是 Hashtable ,它是JDK 1.0的一部分. Hashtable 提供了一种易于使用的.线程安全的.关联的map功能,这当然也是方便的.然而,线程安全性是凭代价换来的―― Hashtable 的所有方法都是同步的. 此时,无竞争的同步会导致可观的性能代价. Hashtable 的后继者 HashMap 是作为JDK1.2中的集合框架的一部分出现的,它通过提供一个不同步的基类和一个同步的包装器 Collections.synchronizedMap

Map m = Collections.synchronizedMap(new HashMap())

Collections.synchronizedMap(new HashMap())让你创建的new HashMap()支持多线程数据的同步.保证多线程访问数据的一致性 来源:http://www.blogjava.net/zlsunnan/archive/2006/07/02/56184.html 在Java类库中出现的第一个关联的集合类是Hashtable,它是JDK 1.0的一部分.Hashtable提供了一种易于使用的.线程安全的.关联的map功能,这当然也是方便的.然而,线程安全性是凭

Java 理论与实践: 并发集合类

Java 理论与实践: 并发集合类 DougLea的 util.concurrent 包除了包含许多其他有用的并发构造块之外,还包含了一些主要集合类型 List 和 Map 的高性能的.线程安全的实现.在本月的 Java理论与实践中,BrianGoetz向您展示了用 ConcurrentHashMap 替换 Hashtable 或 synchronizedMap ,将有多少并发程序获益.您可以在本文的 中与作者以及其他读者共享您的想法(您也可以点击文章顶部或者底部的 讨论进入论坛). 在Java

Java并发编程:并发容器之ConcurrentHashMap

下面这部分内容转载自: http://www.haogongju.net/art/2350374 JDK5中添加了新的concurrent包,相对同步容器而言,并发容器通过一些机制改进了并发性能.因为同步容器将所有对容器状态的访问都 串行化了,这样保证了线程的安全性,所以这种方法的代价就是严重降低了并发性,当多个线程竞争容器时,吞吐量严重降低.因此Java5.0开 始针对多线程并发访问设计,提供了并发性能较好的并发容器,引入了java.util.concurrent包.与Vector和Hasht

ConcurrentHashMap源码剖析

原文出处: 我爱物联网 ConcurrentHashMap是Java5中新增加的一个线程安全的Map集合,可以用来替代HashTable.对于ConcurrentHashMap是如何提高其效率的,可能大多人只是知道它使用了多个锁代替HashTable中的单个锁,也就是锁分离技术(Lock Stripping).实际上,ConcurrentHashMap对提高并发方面的优化,还有一些其它的技巧在里面(比如你是否知道在get操作的时候,它是否也使用了锁来保护?). ConcurrentMap 提供其