zoukankan      html  css  js  c++  java
  • ConcurrentHashMap实现原理

    ConcurrentHashMap采用了分段加锁的方式
    看看get操作hashTable和ConcurrenHashMap的区别
    public synchronized V get(Object key) {
            Entry tab[] = table;
            int hash = hash(key);
            int index = (hash & 0x7FFFFFFF) % tab.length;
            for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
                if ((e.hash == hash) && e.key.equals(key)) {
                    return e.value;
                }
            }
            return null;
    }
    
    而在ConcurrentHashMap当中,是通过Segment的get操作获取的

    问:ConcurrentHashMap的get操作有没有加锁?
    答:并没有
    问:那怎么实现的并发安全准确的读取数据呢?
    答:当出现有key,但是没有value的情况时,将加lock锁,等待value值写入,再读取,防止读不到最新的值。

    问:ConcurrentHashMap有哪些参数可以在构造方法中设置?
    答:concurrencyLevel 并发度,也就是segment的段数 loadfactor 加载因子,进行扩容的阀值因子 initialCapacity 初始化总的线性链的个数,平均分配到每个segment当中
    问:为什么concurrencyLevel 和每个segment当中的链表数都设置成2的n次方形式?
    为了根据hash值得前缀或者后缀快速定位所属的segment和线性链

    static final class HashEntry<K,V> {
        final K key;
        final int hash;
        volatile V value;
        final HashEntry<K,V> next;
    }
    
    HashEntry节点key hash next 都定义成final为什么? volatile修饰value为什么?
    为了防止链表结构被破坏,出现ConcurrentModification的情况。

    -------------------------------
    //返回键值key映射的value值,当不包含key键时返回null
    public V get(Object key) {
            int hash = hash(key.hashCode());
            return segmentFor(hash).get(key, hash);
    }
    

     /* Specialized implementations of map methods */
    
            V get(Object key, int hash) {
                if (count != 0) { // read-volatile
                    HashEntry<K,V> e = getFirst(hash);
                    while (e != null) {
                        if (e.hash == hash && key.equals(e.key)) {
                            V v = e.value;
                            if (v != null)
                                return v;
                            return readValueUnderLock(e); // recheck
                        }
                        e = e.next;
                    }
                }
                return null;
            }
    

    Segment的get操作没有加锁又是怎么实现并发的呢?
    ConcurrentHashMap的get操作是直接委托给Segment的get方法

    get操作不需要锁。

    第一步是访问count变量,这是一个volatile变量,由于所有的修改操作在进行结构修改时都会在最后一步写count变量,通过这种机制保证get操作能够得到最新的结构更新。

    下面都是Segment内部的变量和方法

    /**
     * The number of elements in this segment's region.
     */
       transient volatile int count;
    

     /**
     *   Returns properly casted first entry of bin for given hash.
     */
      HashEntry<K,V> getFirst(int hash) {
           HashEntry<K,V>[] tab = table;
           return tab[hash & (tab.length - 1)];
      }
    

    链表节点

    static final class HashEntry<K,V> {
            final K key;
            final int hash;
            volatile V value;
            final HashEntry<K,V> next;
    
            HashEntry(K key, int hash, HashEntry<K,V> next, V value) {
                this.key = key;
                this.hash = hash;
                this.next = next;
                this.value = value;
            }
    
            @SuppressWarnings("unchecked")
            static final <K,V> HashEntry<K,V>[] newArray(int i) {
                return new HashEntry[i];
            }
        }
    

    对于非结构更新,也就是结点值的改变,由于HashEntry的value变量是volatile的,也能保证读取到最新的值。


    接下来就是对hash链进行遍历找到要获取的结点,如果没有找到,直接访回null。对hash链进行遍历不需要加锁的原因在于链指针next是final的


    但是头指针却不是final的,这是通过getFirst(hash)方法返回,也就是存在table数组中的值。这使得getFirst(hash)可能返回过时的头结点,


    例如,当执行get方法时,刚执行完getFirst(hash)之后,另一个线程执行了删除操作并更新头结点,这就导致get方法中返回的头结点不是最新的。这是可以允许,通过对count变量的协调机制,get能读取到几乎最新的数据,虽然可能不是最新的。要得到最新的数据,只有采用完全的同步。

    最后,如果找到了所求的结点,判断它的值如果非空就直接返回,否则在有锁的状态下再读一次。这似乎有些费解,理论上结点的值不可能为空,这是因为put的时候就进行了判断,如果为空就要抛NullPointerException。空值的唯一源头就是HashEntry中的默认值,因为HashEntry中的value不是final的,非同步读取有可能读取到空值。仔细看下put操作的语句:tab[index] = new HashEntry<K,V>(key, hash, first, value),在这条语句中,HashEntry构造函数中对value的赋值以及对tab[index]的赋值可能被重新排序,这就可能导致结点的值为空。这种情况应当很罕见,一旦发生这种情况,ConcurrentHashMap采取的方式是在持有锁的情况下再读一遍,这能够保证读到最新的值,并且一定不会为空值。

  • 相关阅读:
    提高程序开发效率的文章
    动网代码备忘录
    asp.net 优化ASP.NET应用程序性能研究与探讨
    如何提高写程序的效率与减少 bug 的错误率
    .NET之默认依赖注入
    linux挂载windows共享文件夹
    如何修改数据库时区
    Oracle 数据库11g新特性之高效 PL/SQL 编码
    Oracle Data Guard 理论知识
    (转)关于PL/SQL Developer中对存储过程add debug information
  • 原文地址:https://www.cnblogs.com/clovejava/p/7812065.html
Copyright © 2011-2022 走看看