HashMap中未进行同步考虑,而Hashtable在每个方法上加上了synchronized,锁住了整个Hash表,一个时刻只能有一个线程操作,其他的线程则只能等待,在并发的环境下,这样的操作导致Hashtable的效率低下。

  Collections的静态方法synchronizedMap(HashMap hm)返回的是一个SynchronizedMap对象,SynchronizedMap也是对原HashMap中的方法加上synchronized,锁的粒度应该减小。

  ConcurrentHashMap将原来的原来的Hash表分成了16个桶(默认值),桶内的读操作是不加锁的,桶内的写操作是加上了锁的。这样的实现允许更多的线程同时地操作Hash表,在并发的环境下,应该选择的是ConcurrentHashMap。

  

  ConcurrentHashMap的get读操作中基本没有用到锁,可以看下面的代码:

  1. V get(Object key, int hash) {
  2. if (count != 0) { // read-volatile
  3. HashEntry<K,V> e = getFirst(hash);
  4. while (e != null) {
  5. if (e.hash == hash && key.equals(e.key)) {
  6. V v = e.value;
  7. if (v != null)
  8. return v;
  9. return readValueUnderLock(e); // recheck
  10. }
  11. e = e.next;
  12. }
  13. }
  14. return null;
  15. }

  当读到的value是null时,处理是return readValueUnderLock(e);,因为整个的操作未加锁,上面的if (v != null)为false时,可能同时有其他线程修改了value的值,这里有进行一次同步的取值,如下:

  1. V readValueUnderLock(HashEntry<K,V> e) {
  2. lock();
  3. try {
  4. return e.value;
  5. } finally {
  6. unlock();
  7. }
  8. }

  size读操作为了确保读的数据是准确的也进行了部分的加锁操作。

  写后读或者读后写都会造成数据的不一致,即使用线程安全类,应该做好对象的加锁。

  ConcurrentHashMap的写操作锁住了Segment 

  1. public V put(K key, V value) {
  2. if (value == null)
  3. throw new NullPointerException();
  4. int hash = hash(key.hashCode());
  5. return segmentFor(hash).put(key, hash, value, false);
  6. }

  下面是针对Segment的写操作:

  1. V put(K key, int hash, V value, boolean onlyIfAbsent) {
  2. lock();
  3. try {
  4. int c = count;
  5. if (c++ > threshold) // ensure capacity
  6. rehash();
  7. HashEntry<K,V>[] tab = table;
  8. int index = hash & (tab.length - 1);
  9. HashEntry<K,V> first = tab[index];
  10. HashEntry<K,V> e = first;
  11. while (e != null && (e.hash != hash || !key.equals(e.key)))
  12. e = e.next;
  13.  
  14. V oldValue;
  15. if (e != null) {
  16. oldValue = e.value;
  17. if (!onlyIfAbsent)
  18. e.value = value;
  19. }
  20. else {
  21. oldValue = null;
  22. ++modCount;
  23. tab[index] = new HashEntry<K,V>(key, hash, first, value);
  24. count = c; // write-volatile
  25. }
  26. return oldValue;
  27. } finally {
  28. unlock();
  29. }
  30. }

  CopyOnWriteArrayList应用于读多写少的场景,对读操作不加锁,对写操作,先复制一份新的集合,在新的集合上面修改,然后将新集合赋值给旧的引用,并通过volatile 保证其可见性,当然写操作的锁是必不可少的了。

  CopyOnWriteArrayList应用于读多写少的场景,在有较多写操作的情况下,CopyOnWriteArrayList性能不佳,而且如果容器容量较大的话容易造成溢出,应该使用Vector。使用ReadWriteLock是另外一种思路。

  下面是的CopyOnWriteArrayList的set操作:

  1. public E set(int index, E element) {
  2. final ReentrantLock lock = this.lock;
  3. lock.lock();
  4. try {
  5. Object[] elements = getArray();
  6. E oldValue = get(elements, index);
  7.  
  8. if (oldValue != element) {
  9. int len = elements.length;
  10. Object[] newElements = Arrays.copyOf(elements, len);
  11. newElements[index] = element;
  12. setArray(newElements);
  13. } else {
  14. // Not quite a no-op; ensures volatile write semantics
  15. setArray(elements);
  16. }
  17. return oldValue;
  18. } finally {
  19. lock.unlock();
  20. }
  21. }

   Arrays.copyOf创建一个新的数组,在新数组上做了修改后将新数组的引用赋给原对象。

  1. private transient volatile Object[] array;
  2.  
  3. final void setArray(Object[] a) {
  4. array = a;
  5. }

  set方法中的

  1.  else {
  2. // Not quite a no-op; ensures volatile write semantics
  3. setArray(elements);
  4. }

  元素都没有发生变化,为什么还要重新做一次赋值操作呢。

  为了保持“volatile”的语义,任何一个读操作都应该是一个写操作的结果,也就是说线程的读操作看到的数据一定是某个线程写操作的结果。这里即使不设置也没有问题,仅仅是为了一个语义上的补充。

  LinkedHashMap实现与HashMap的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。
  
  注意,此实现不是同步的。如果多个线程同时访问链接的哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步。

  LinkedHashMap中的Entry是这样的,维护了before Entry和after Entry。

  1. static class Entry<K,V> extends HashMap.Node<K,V> {
  2. Entry<K,V> before, after;
  3. Entry(int hash, K key, V value, Node<K,V> next) {
  4. super(hash, key, value, next);
  5. }
  6. }

ConcurrentHashMap、CopyOnWriteArrayList、LinkedHashMap的更多相关文章

  1. Collections.synchronizedList 、CopyOnWriteArrayList、Vector介绍、源码浅析与性能对比

    ## ArrayList线程安全问题 众所周知,`ArrayList`不是线程安全的,在并发场景使用`ArrayList`可能会导致add内容为null,迭代时并发修改list内容抛`Concurre ...

  2. 【Java】Map杂谈,hashcode()、equals()、HashMap、TreeMap、LinkedHashMap、ConcurrentHashMap

    参考的优秀文章: <Java编程思想>第四版 <Effective Java>第二版 Map接口是映射表的结构,维护键对象与值对象的对应关系,称键值对. > hashco ...

  3. HashMap、LinkedHashMap、ConcurrentHashMap、ArrayList、LinkedList 底层实现

    HashMap相关问题 1.你用过HashMap吗?什么是HashMap?你为什么用到它? 用过,HashMap是基于哈希表的Map接口的非同步实现,它允许null键和null值,且HashMap依托 ...

  4. 牛客网Java刷题知识点之Map的两种取值方式keySet和entrySet、HashMap 、Hashtable、TreeMap、LinkedHashMap、ConcurrentHashMap 、WeakHashMap

    不多说,直接上干货! 这篇我是从整体出发去写的. 牛客网Java刷题知识点之Java 集合框架的构成.集合框架中的迭代器Iterator.集合框架中的集合接口Collection(List和Set). ...

  5. 003-jdk-数据结构-HashMap、HashTable、ConcurrentHashMap、TreeMap、LinkedHashMap、Set

    一.Map概述 Map:“键值”对映射的抽象接口.该映射不包括重复的键,一个键对应一个值. 1.1.HashTable[不常用] 基于“拉链法”实现的散列表. 底层数组+链表实现,无论key还是val ...

  6. java面试记录三:hashmap、hashtable、concurrentHashmap、ArrayList、linkedList、linkedHashmap、Object类的12个成员方法、消息队列MQ的种类

    口述题 1.HashMap的原理?(数组+单向链表.put.get.size方法) 非线程安全:(1)hash冲突:多线程某一时刻同时操作hashmap并执行put操作时,可能会产两个key的hash ...

  7. hashMap、ConcurrentHashMap、hashTable、TreeMap、LinkedHashMap用法区别详解

    Java集合中设计了一个接口Java.util.Map,它实现类中hashMap.hashTable.TreeMap.ConcurrentHashMap.LinkedHashMap. Map类型的集合 ...

  8. Hashtable、synchronizedMap、ConcurrentHashMap 比较

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp18 Hashtable.synchronizedMap.Concurren ...

  9. HashMap、Hashtable、 LinkedHashMap、TreeMap四者之分。

    java为数据结构中的映射定义了一个接口java.util.Map,此接口主要有四个常用的实现类,分别是HasMap.Hashtable.LinkedHasmap和TreeMap. (1)HashMa ...

随机推荐

  1. 2017 CCPC 哈尔滨站 HDU 6242

    Geometry Problem Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 262144/262144 K (Java/Other ...

  2. 基于.netcore 开发的轻量Rpc框架

    Rpc原理详解 博客上已经有人解释的很详细了,我就不在解释了.传送门 项目简介 项目是依赖于.net core2.0版本,内部都是依靠IOC来实现的,方便做自定义扩展.底层的通信是采用socket,s ...

  3. POI实现大数据EXCLE导入导出,解决内存溢出问题

    使用POI能够导出大数据保证内存不溢出的一个重要原因是SXSSFWorkbook生成的EXCEL为2007版本,修改EXCEL2007文件后缀为ZIP打开可以看到,每一个Sheet都是一个xml文件, ...

  4. PHP和Python如何选择?或许可以考虑这三个问题

    撤稿纠错 文/黄小天.李亚洲 (选自Hackernoon 机器之心编译) 2017 年可谓是网页应用与 API 之年,开发者不用每次重新发明轮子,而是利用脚手架和第三方库就能确保项目在几天内实时部署. ...

  5. Java数据结构和算法(十三)——哈希表

    Hash表也称散列表,也有直接译作哈希表,Hash表是一种根据关键字值(key - value)而直接进行访问的数据结构.它基于数组,通过把关键字映射到数组的某个下标来加快查找速度,但是又和数组.链表 ...

  6. ::在c++中什么意思

    (1)表示“域操作符” 例:声明了一个类A,类A里声明了一个成员函数void f(),但没有在类的声明里给出f的定义,那么在类外定义f时, 就要写成void A::f(),表示这个f()函数是类A的成 ...

  7. 为什么选择.NETCore?

    为什么.NETCore? 学习新的开发框架是一项巨大的投资.您需要学习如何在新框架中编写,构建,测试,部署和维护应用程序.作为开发人员,有许多框架可供选择,很难知道什么是最适合的工作.即使您正在使用. ...

  8. python3 第十九章 - 写一个10进制转任意进制的函数

    我们先回忆下之前所学的进制转换的知识(详见:第十章),10进制转其它进制的方法是: 整数部分,除基取余,逆序排列 小数部分,乘基取整,顺序排列 负数,按绝对值处理 好,假设我们需要转化的数都是正整数, ...

  9. 【深度学习系列】迁移学习Transfer Learning

    在前面的文章中,我们通常是拿到一个任务,譬如图像分类.识别等,搜集好数据后就开始直接用模型进行训练,但是现实情况中,由于设备的局限性.时间的紧迫性等导致我们无法从头开始训练,迭代一两百万次来收敛模型, ...

  10. redis五大类型用法

    Redis五大类型:字符串(String).哈希/散列/字典(Hash).列表(List).集合(Set).有序集合(sorted set)五种Controller:@Resource RedisTe ...