zoukankan      html  css  js  c++  java
  • Java容器:HashTable, synchronizedMap与ConcurrentHashMap

    首先需要明确的是,不管使用那种Map,都不能保证公共混合调用的线程安全,只能保证单条操作的线程安全,在这一点上各Map不存在优劣。

    前文中简单说过HashTable和synchronizedMap,其实这两个类不需要说太多,把代码贴一下相信看过Java多线程的就能很容易理解了。

    HashTable

    HashTable的话,实现这个样子的。可以看到的是,对于Hash表的所有操作,HashTable都加了锁,但也只能保证单条操作的线程安全。

    public synchronized V get(Object key) {
           // 省略实现
    }
    public synchronized V put(K key, V value) {
        // 省略实现
    }
    

    synchronizedMap

    synchronizedMap的实现如下,没直接在方法上加,尽管其实质与HashTable是等效的,也同样有HashTable的缺陷,但synchronizedMap给用户留下了选择的空间:用户可以在不需要加锁时直接操作原始Map,在实际编码时就可以基于这点进行优化。

    // synchronizedMap方法
    public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
           return new SynchronizedMap<>(m);
       }
    // SynchronizedMap类
    private static class SynchronizedMap<K,V>
           implements Map<K,V>, Serializable {
           private static final long serialVersionUID = 1978198479659022715L;
    
           private final Map<K,V> m;     // Backing Map
           final Object      mutex;        // Object on which to synchronize
    
           SynchronizedMap(Map<K,V> m) {
               this.m = Objects.requireNonNull(m);
               mutex = this;
           }
    
           SynchronizedMap(Map<K,V> m, Object mutex) {
               this.m = m;
               this.mutex = mutex;
           }
    
           public int size() {
               synchronized (mutex) {return m.size();}
           }
           public boolean isEmpty() {
               synchronized (mutex) {return m.isEmpty();}
           }
           public boolean containsKey(Object key) {
               synchronized (mutex) {return m.containsKey(key);}
           }
           public boolean containsValue(Object value) {
               synchronized (mutex) {return m.containsValue(value);}
           }
           public V get(Object key) {
               synchronized (mutex) {return m.get(key);}
           }
    
           public V put(K key, V value) {
               synchronized (mutex) {return m.put(key, value);}
           }
           public V remove(Object key) {
               synchronized (mutex) {return m.remove(key);}
           }
           // 省略其他方法
    }
    

    ConcurrentHashMap

    提高安全HashMap的并发性的方法,可以通过减小锁粒度的方式,不对整个Hash表加锁,而是对每个bucket加锁,甚至用锁池,每个锁维护几个bucket,让Map的不同部分可以被多个线程访问,不过这样的方式会让对整体集合操作的方法的实现更加困难。Java7中的ConcurrentHashMap就通过Segment引入了这个分段加锁概念,但Java8由于上述困难更改了机制,引入了红黑树结构,去掉了Segment。

    JDK1.8的改进后,ConcurrentHashMap的写性能有10%左右的降低,但读性能有了很大提升。主要是将过于集中的hash节点的效率从O(N)提高到了O(LOGN)。

    ConcurrentHashMap利用了CAS进行实现,从而以乐观锁的方式实现了线程安全的HashMap,concurrentHashMap的源码很复杂,一些方法的实现思路如下:

    Java8的ConcurrentHashMap的数据结构实现思路大概为,对于Hash表中每一个节点,其数据结构可以为单节点,链表数组或红黑树,随着节点中元素增加而改变。(改变方法见treeifyBin)。

    put()方法

    • hash数组是否为空,为空则先调用initTable()方法进行初始化
    • 如果hash数组已经初始化了,则根据hash值找到对应的数组下标,如果对应节点为空,通过cas方式直接插入
    • 如果数组已经扩容,则进行数据迁移
    • 如果数组该位置已经有值了,则需要对该节点加锁并进行数据插入操作,仅对一个节点加锁,其锁粒度实际上比Java7中Segment实现更小。此时如果该节点是链表结构,则遍历链表,插入数据;如果如果该节点是红黑树结构,则调用红黑树的插值方法插入新值
    • 针对链表结构,如果插入新元素后,hash数组长度超过阈值,则需要调用treeifyBin()方法进行扩容或者是将链表转换为红黑树

    initTable()方法

    • 当table不存在,开始自旋。
    • 利用CAS操作将sizeCtl属性设置为-1,表示本线程正对数组初始化,阻止其他线程的初始化。
    • 进行常规的初始化操作,扩容阈值为数组容量的75%。
    • 将sizeCtl设置成扩容阈值,结束初始化。

    treeifyBin()方法

    该方法用于对数组链表扩容,或将链表结构转化为红黑树,一个节点的元素个数大于链表阈值(默认8)时,如果数组链表长度小于红黑树阈值(默认64),则对数组链表扩容,否则将该节点转换为红黑树。

    transfer(),helpTransfer(),tryPresize()方法

    这些方法负责hash表扩容,由于要通过CAS实现线程安全,代码十分复杂。大概思路为,原数组长度为n,则产生n个迁移任务,让每一个线程负责一个小任务,之后监测是否有其他没做完的任务,帮助迁移。

    get()方法

    get方法不涉及CAS操作,实现较为简单,计算hash值,找到对应节点进行判断:

    • 该位置为null返回null。
    • 该位置节点为所求值,返回值。
    • 该位置节点hash值小于0,说明在扩容,或者为红黑树,使用find方法。
    • 以上都不满足,该位置为链表,遍历搜索。

    性能

    目前多线程环境下ConcurrentMap的性能有很高的优越性,通常情况下,如果你的Map处于多读少写的场景,优先考虑ConcurrentMap,但在多写少读的情境中,由于资源竞争激烈,CAS自旋可能导致ConcurrentMap性能不如synchronizedMap。

    参考文献

    Collections.synchronizedMap()、ConcurrentHashMap、Hashtable之间的区别
    Java8 ConcurrentHashMap详解
    浅谈Java8中的ConcurrentHashMap
    SynchronizedMap

  • 相关阅读:
    mysql--连接查询(内外连接)
    Mysql--select基础查询
    Mysql--数据定义语言(DDL)
    Mysql--数据操作语言(DML)
    java--String、StringBuilder、StringBuffer的解析和比较?
    Java--equals和 == 的比较和equals()、HashCode()的重写
    Mysql--数据类型
    Mysql--约束
    SpringCloud版本说明
    springBoot 发送邮件
  • 原文地址:https://www.cnblogs.com/cielosun/p/10585973.html
Copyright © 2011-2022 走看看