zoukankan      html  css  js  c++  java
  • 高并发第九弹:逃不掉的Map --> HashMap,TreeMap,ConcurrentHashMap

    平时大家都会经常使用到 Map,面试的时候又经常会遇到问Map的,其中主要就是 ConcurrentHashMap,在说ConcurrentHashMap.我们还是先看一下,

    其他两个基础的 Map 类: HashMap  和 TreeMap

    HashMap: 

    public class HashMap<K,V> extends AbstractMap<K,V>
        implements Map<K,V>, Cloneable,Serializable {   // 这里有个很逗的事情 hashMap 继承了AvstractMap 为什么还要实现Map? 据说,作者说的 这只是个错误的写法 Q_Q

    TreeMap:

    public class TreeMap<K,V>
        extends AbstractMap<K,V>
        implements NavigableMap<K,V>, Cloneable, java.io.Serializable

    public interface NavigableMap<K,V> extends SortedMap<K,V> 
      实现 存储 遍历 性能损耗 键值对 安全 效率
    TreeMap SortMap接口,基于红黑树  默认按键的升序排序  Iterator遍历是排序的 插入、删除 键、值都不能为null  非并发安全Map  适用于在Map中插入、删除和定位元素
    HashMap 基于哈希散列表实现 随机存储 Iterator遍历是随机的  基本无 只允许键、值均为null 非并发安全Map 适用于按自然顺序或自定义顺序遍历键(key)

    HashMap通常比TreeMap快一点(树和哈希表的数据结构使然),建议多使用HashMap,在需要排序的Map时候才用TreeMap。

    那么现在就聊一下 HashMap和ConcurrentHashMap

    我们都知道ConcurrentHashMap 是线程安全的.那为什么HashMap就线程不安全了呢?
    这里有很不错的解释https://my.oschina.net/hosee/blog/673521
    还有一个路径太长了.给个短的 还可以的
    总结起来就是:
      
    1. resize死循环
        我们都知道HashMap初始容量大小为16,一般来说,当有数据要插入时,都会检查容量有没有超过设定的thredhold,如果超过,需要增大Hash表的尺寸,但是这样一来,整个Hash表里的元素都需要被重算一遍。这叫rehash,这个成本相当的大。
    在rehash的时候,在多线程的时候容易造成环形链表
      2.fail-fast
       如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。

    这个异常意在提醒开发者及早意识到线程安全问题,具体原因请查看ConcurrentModificationException的原因以及解决措施  看了这个 我觉得需要去修改一下我原来说的CopyAndWriteArrayList 了

    ConcurrentHashMap来了.面试以前遇到了很多次

    (1)结构 [Java7与Java8不同]

    JDK7

    1.ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)

    2 . 当我们读取某个Key的时候它先取出key的Hash值,并将Hash值得高sshift位与Segment的个数取模,决定key属于哪个Segment。接着像HashMap一样操作Segment。 为了保证不同的Hash值保存到不同的Segment中,ConcurrentHashMap对Hash值也做了专门的优化。

    3. 如果并发度设置的过小,会带来严重的锁竞争问题;如果并发度设置的过大,原本位于同一个Segment内的访问会扩散到不同的Segment中,CPU cache命中率会下降,从而引起程序性能下降。(文档的说法是根据你并发的线程数量决定,太多会导性能降低)

    2. JDK8中的实现

    ConcurrentHashMap在JDK8中进行了巨大改动,很需要通过源码来再次学习下Doug Lea的实现方法。

    它摒弃了Segment(锁段)的概念,而是启用了一种全新的方式实现,利用CAS算法。它沿用了与它同时期的HashMap版本的思想,底层依然由“数组”+链表+红黑树的方式思想(JDK7与JDK8中HashMap的实现),但是为了做到并发,又增加了很多辅助的类,例如TreeBin,Traverser等对象内部类。

    总结

    JDK6,7中的ConcurrentHashmap主要使用Segment来实现减小锁粒度,把HashMap分割成若干个Segment,在put的时候需要锁住Segment,get时候不加锁,使用volatile来保证可见性,当要统计全局时(比如size),首先会尝试多次计算modcount来确定,这几次尝试中,是否有其他线程进行了修改操作,如果没有,则直接返回size。如果有,则需要依次锁住所有的Segment来计算。

    jdk7中ConcurrentHashmap中,当长度过长碰撞会很频繁,链表的增改删查操作都会消耗很长的时间,影响性能,所以jdk8 中完全重写了concurrentHashmap,代码量从原来的1000多行变成了 6000多 行,实现上也和原来的分段式存储有很大的区别。

    主要设计上的变化有以下几点:

    1. 不采用segment而采用node,锁住node来实现减小锁粒度。
    2. 设计了MOVED状态 当resize的中过程中 线程2还在put数据,线程2会帮助resize。
    3. 使用3个CAS操作来确保node的一些操作的原子性,这种方式代替了锁。
    4. sizeCtl的不同值来代表不同含义,起到了控制的作用。

    至于为什么JDK8中使用synchronized而不是ReentrantLock,我猜是因为JDK8中对synchronized有了足够的优化吧。

    总结: 

    HashMap非线程安全、ConcurrentHashMap线程安全      (可以看下这个,很有ConcurrentHashMap能完全替代HashTable吗?)

    HashMap允许Key与Value为空,ConcurrentHashMap不允许

    HashMap不允许通过迭代器遍历的同时修改,ConcurrentHashMap允许。并且更新可见

  • 相关阅读:
    【Java基础】-switch支持的数据类型
    【微服务】微服务间的通信方式
    MyBatis中 #{}与${}的区别
    exeplorer-损坏的映像【错误状态0xc000012f】
    Java异常-常见的运行时异常和非运行时异常
    jvm-GC集合篇一之GC查找可回收垃圾算法
    JVM-栈的内存结构之--方法返回地址
    JVM-虚拟机栈的内部结构之局部变量表
    jvm-虚拟机栈(java栈)
    JVM-运行时数据区之---PC寄存器
  • 原文地址:https://www.cnblogs.com/aihuxi/p/9688711.html
Copyright © 2011-2022 走看看