zoukankan      html  css  js  c++  java
  • ConcurrentHashMap详解

    ConcurrentHashMap详解


    注:该文章主要讲的是JDK1.6中ConcurrentHashMap的实现,JDK1.8中ConcurrentHashMap的实现由不同的机制,详解可看:ConcurrentHashMap总结

    1 概述

    public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>, Serializable {

    • ConcurrentHashMap内部使用了分段锁+哈希数组+链表的实现(JDK1.6)
    • ConcurrentHashMap是遍历无序、线程安全
    • ConcurrentHashMap的迭代器不是快速失败的,也就是说我们在遍历的时候可以对ConcurrentHashMap的结构进行修改,而不会抛出 ConcurrentModificationException异常
    • HashMap中key不能有重复元素,并且key与value都不能为null

    2 实现机制


    2.1 锁分段技术(分拆锁技术)
    《Java并发编程艺术》

    HashTable容器在竞争激烈的并发环境下表现出效率低下的原因是所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一>部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞>>争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段>技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

    《Java并发编程实践》

    分拆锁(lock spliting)就是若原先的程序中多处逻辑都采用同一个锁,但各个逻辑之间又相互独立,就可以拆(Spliting)为使用多个锁,每个锁守护不同的逻辑。 分拆锁有时候可以被扩展,分成可大可小加锁块的集合,并且它们归属于相互独立的对象,这样的情况就是分离锁(lock striping)。

    2.2 ConcurrentHashMap结构
    ConcurrentHashMap的结构与HashMap的结构类似,只是在中间加了一层锁(Segment)。其类图如下:
    ConcurrentHashMap类图(图来自《Java并发编程的艺术》)

    ConcurrentHashMap是有Segment数组组成,每个Segment由HashEntry数组组成,每个HashEntry数组中存放的是HashEntry链表。其中Segment是可重入锁ReentrantLock,它代表锁结构,每个Segment守护它自己包含的HashEntry数组中的元素,这样就体现出锁分段技术。我们在并发访问时,该Segment锁仅仅锁住它自己的一部分数据,从而不会影响其他部分的数据的读写。其结构图如下:
    enter description here

    3 一些类的结构分析

    注:自己在分析时,有许多的方法都没有分析,主要分析了几个方法的逻辑,其他方法类似。下面的类来自JDK1.6,自己删除了一些字段与方法。


    3.1 HashEntry类

    • HashEntry用来封装key、value
    • key,hash 和 next 域都被声明为 final 型,至于为何为 final变量,后面会分析
    • value 域被声明为 volatile 型,至于为何为 volatile 变量,后面会分析
      注:next申明为final类型,说明HashEntry链表只能从头插入,而且不能删除。
    static final class HashEntry<K,V> {
        final K key;	
        final int hash;
        volatile V value;		// value 声明为 volatile 类型
        final HashEntry<K,V> next;	// next 申明为 final 类型
    
        HashEntry(K key, int hash, HashEntry<K,V> next, V value) {
            this.key = key;
            this.hash = hash;
            this.next = next;
            this.value = value;
        }
    }
    

    3.2 Segment类

    • Segment 类继承 ReentrantLock,用来守护其含有的数据
    • count 代表该 Segment 中 HashEnty 的数量,是 volatile 类型。
    • modCount 代表该 Segment 结构改变的次数
    • loadFactor 代表负载因子;threshold 代表该 Segment 最大容量(这两个字段与HashMap中意义相似,用来扩容的)
    static final class Segment<K,V> extends ReentrantLock implements Serializable { 
            /** 
             * 该 Segment 中包含的 HashEntry 元素的个数
             * 该变量被声明为 volatile 型
             */ 
            transient volatile int count; 
            /** 
             * 该 Segment 结构改变的次数
             */ 
            transient int modCount; 
            /** 
             * 该 Segment 的最大容量
             */ 
            transient int threshold; 
            /** 
             * table 是由 HashEntry 数组,用来存放HashEnty链表
             */ 
            transient volatile HashEntry<K,V>[] table; 
            /** 
             * 加载因子
             */ 
            final float loadFactor; 
            /** 
             * 根据 key 的散列值,找到该散列值对于的 HashEntry 链表
             */ 
            HashEntry<K,V> getFirst(int hash) { 
                HashEntry<K,V>[] tab = table; 
                return tab[hash & (tab.length - 1)]; 
            } 
    	/** 
             * 初始化 HashEntry 数组,设置加载因子 loadFactor
             * 计算最大容量 = HashEntry 数组长度 * 加载因子 loadFactor
             */ 
            Segment(int initialCapacity, float lf) {
                ...
            }        
            /**
             * 下面以下方法主要是实现Map的操作的方法,如put、get等等,
             * 方法没有写完,后面会详细分析几个方法
             * ConcurrentHashMap 对数据的操作其实主要是在 Segment 中对数据
             * 操作的体现。
             */        
    	/* Specialized implementations of map methods */
            V get(Object key, int hash) {
                ...
            }	
    	boolean containsKey(Object key, int hash) {
                ...
            }
    }
    

    3.3 ConcurrentHashMap 类
    注:还有许多的方法没有列出来

    public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> 
           implements ConcurrentMap<K, V>, Serializable { 
       /** 
        * 由 Segment 对象组成的数组
        */ 
        final Segment<K,V>[] segments; 
       /** 
        * 该方法会初始化默认初始容量 (16)、默认加载因子 (0.75) 和 默认并发级别 (16) 
        * 的空散列映射表。主要功能就是初始化 Segment 数组
        */ 
        public ConcurrentHashMap() { 
            ...
        }
        /**
         * 散列算法
         */
        private static int hash(int h) {
            // Spread bits to regularize both segment and index locations,
            // using variant of single-word Wang/Jenkins hash.
            h += (h <<  15) ^ 0xffffcd7d;
            h ^= (h >>> 10);
            h += (h <<   3);
            h ^= (h >>>  6);
            h += (h <<   2) + (h << 14);
            return h ^ (h >>> 16);
        }
        /**
         * 通过散列值计算出在哪个 Segment 中
         */
        final Segment<K,V> segmentFor(int hash) {
            return segments[(hash >>> segmentShift) & segmentMask];
        }
        /**
         * ConcurrentHashMap 的方法,如put、get等等
         * 方法没有展示完
         */
        public V put(K key, V value) {
            ....
        }
    }
    

    4 具体的操作


    注:这里要写 ConcurrentHashMap 的线程安全是怎么实现的,需要对JMM(Java内存模型)、volatile 变量、happens-before 等等并发知识进行了解,具体可见:Java并发编程(一)

    4.1 put 操作
    首先通过 key 的哈希值找到其对应的 Segment,然后该 Segment 对于的 put 方法

    public V put(K key, V value) {
        if (value == null)
            throw new NullPointerException();
        // 通过散列算法计算 key 的散列值
        int hash = hash(key.hashCode());
        // 根据散列码找到对应的 Segment
        return segmentFor(hash).put(key, hash, value, false);
    }
    

    Segment 中的 put 方法

    V put(K key, int hash, V value, boolean onlyIfAbsent) {
        // 获取锁,这里只是把该 Segment 锁住了,体现了分段锁的思想
        lock();
        try {
            // count代表该 Segment 中HashEntry数目,其是volatile变量
            // 注意:这里不能直接 count++,因为单个volatile变量的写才有原子性
            // 像 volatile++ 这种复合操作是没有原子性的
            int c = count;
            // 判断该 Segment 能否存放
            if (c++ > threshold) // ensure capacity
                // 扩容
                rehash();
            HashEntry<K,V>[] tab = table;
            int index = hash & (tab.length - 1);
            HashEntry<K,V> first = tab[index];
            HashEntry<K,V> e = first;
            while (e != null && (e.hash != hash || !key.equals(e.key)))
                e = e.next;
            V oldValue;
            // 如果该 key 已经存在,则替换原 value,返回原 value
            if (e != null) {
                oldValue = e.value;
                if (!onlyIfAbsent)
                    e.value = value;
            }
            // 该 key 没有存在,则向 HashMap 链表添加一个 HashMap
            // 注意:只能在链表的头部添加,因为 HashEntry.next 是 final 类型。
            else {
                oldValue = null;
                // 要添加新节点到链表中,链表的结构改变,所以 modCont 要加 1 
                ++modCount;
                tab[index] = new HashEntry<K,V>(key, hash, first, value);
                // 改变 count 值,count 是 volatile 变量 
                // 注意:这里写 volatile 变量,会把变化的值向主内存中写,
                // 并通知读 volatile 变量的线程从主内存中读取
                // 这里与 get() 方法中对 count 的读取形成 happens-before 关系
                count = c; // write-volatile
            }
            return oldValue;
        } finally {
            // 释放锁
            unlock();
        }
    }
    

    注意:这里仅仅对该 Segment 加锁了,没有对整个 ConcurrentHashMap 进行加锁。这里表现出分段锁思想,其它线程依然可以获取其他 Segment 的锁进行写操作。

    扩容:这里是对单独的 Segment 的容量进行扩容,没有对这个容器进行扩容。

    以上我们可以总结出:

    • Segment 数组的长度代表并发级别(理想状态下,数组的长度为 ConcurrentHasMap 可以支持线程并发操作的数目)
    • ConcurrentHashMap 的 size 是每个 Segment 的 count 之和

    4.2 remove 操作
    首先通过 key 的哈希值找到其对应的 Segment,然后该 Segment 对于的 remove 方法

    public V remove(Object key) {
        int hash = hash(key.hashCode());
        return segmentFor(hash).remove(key, hash, null);
    }
    

    Segment 中的 remocve 方法

    V remove(Object key, int hash, Object value) {
        // 获取锁,理由同上
        lock();
        try {
            // 读取 count 值,理由同上
            int c = count - 1;
            HashEntry<K,V>[] tab = table;
            int index = hash & (tab.length - 1);
            HashEntry<K,V> first = tab[index];
            HashEntry<K,V> e = first;
            while (e != null && (e.hash != hash || !key.equals(e.key)))
                e = e.next;
            V oldValue = null;
            if (e != null) {
                V v = e.value;
                // 找到要删除的节点
                if (value == null || value.equals(v)) {
                    oldValue = v;
                    // 要删除一个节点,链表的结构改变,所以 modCont 要加 1 
                    ++modCount;
                    // 这里删除一个节点,写法特别,下面会分析
                    HashEntry<K,V> newFirst = e.next;
                    for (HashEntry<K,V> p = first; p != e; p = p.next)
                        newFirst = new HashEntry<K,V>(p.key, p.hash, newFirst, p.value);
                    tab[index] = newFirst;
                    // 写 count 的值,理由同上
                    count = c; // write-volatile
                 }
             }
            return oldValue;
        } finally {
            unlock();
        }
    }
    

    注意:这里的删除并不是直接的删除该节点,因为 HashEntry.next 是 final 类型,不能直接删除。它首先找到要删除的那个节点,然后把删除那个节点之前的没有节点中的值复制到新节点中,最后构成一个新链条。具体如下:
    执行删除之前的原链表:
    图来自https://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/
    执行删除之后的新链表:
    图来自https://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/

    从上图可以看出,删除节点 C 之后的所有节点原样保留到新链表中;删除节点 C 之前的每个节点被克隆到新链表中,注意:它们在新链表中的链接顺序被反转了。

    在执行 remove 操作时,原始链表的结构并没有被修改,综合上面的分析我们可以看出,写线程对某个链表的结构性修改不会影响其他的并发读线程对这个链表的遍历访问。

    4.3 get 操作
    首先通过 key 的哈希值找到其对应的 Segment,然后该 Segment 对于的 remove 方法

    public V get(Object key) {
        int hash = hash(key.hashCode());
        return segmentFor(hash).get(key, hash);
    }
    

    Segment 中的 get方法

    V get(Object key, int hash) {
        // 读 count 值,count 是 volatile 变量
        // 注意:这里是读 volatile 变量,它会从主内存中读取
        // 它始终会读取到 volatile 更新后的内存中变化
        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;
                // 如果为 null,加锁后读
                return readValueUnderLock(e); // recheck
            }
            e = e.next;
        }
       return null;
    }
    
    /**
     * 加锁读的方法
     */
    V readValueUnderLock(HashEntry<K,V> e) {
        lock();
        try {
            return e.value;
        } finally {
            unlock();
        }
    }
    

    我们可以看到其他的写操作(如 put、remove等)都加锁了的,所以体现出并发性。而这里读(get、contains等)没有进行加锁,这里使用了 volatile 类型的内存可见性:写线程修改了 volatile 变量,读线程一定能够读到修改后的值。

    所以这里可以回答 HashEntry 中 value 为什么是 volatile 变量,以及 count 为什么是 volatile 变量:因为每次写线程修改后的值,读线程一定能够读到。

    至于为什么 HashEntry.next 为何是 final 类型的:因为在修改链表结构时(remove、put等),写线程不会对原链表结构进行修改,所以读线程在不加锁的情况下仍然可以对该链表进行遍历访问。

    4.3 size 操作
    统计整个 ConcurrentHashMap 的 size 大小,就是必须统计每个 Segment 的 count 大小后求和。其中 Segment 中的 count 变量是 volatile 变量,每个 count 变量都是最。但是如果我们累加后,就不一定会得到 ConcurrentHashMap 的大小,因为如果在累加时使用过的 count 发生改变,那么结果就不准确了。如果统计大小时,锁住会导致效率低下。因为在累加 count 的操作过程中,之前累加 count 的值发生变化的几率不大。所以,在 JDK 1.6 中通过连续统计两次,比较两次统计结果,如果发生变化则会加锁方式。

    public int size() {
        final Segment<K,V>[] segments = this.segments;
        long sum = 0;
        long check = 0;
        int[] mc = new int[segments.length];
        // Try a few times to get accurate count. On failure due to
        // continuous async changes in table, resort to locking.
        for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) {
            check = 0;
            sum = 0;
            int mcsum = 0;
            for (int i = 0; i < segments.length; ++i) {
                sum += segments[i].count;
                mcsum += mc[i] = segments[i].modCount;
            }
            if (mcsum != 0) {
                for (int i = 0; i < segments.length; ++i) {
                    check += segments[i].count;
                	if (mc[i] != segments[i].modCount) {
                        check = -1; // force retry
                        break;
                    }
                }
            }
            if (check == sum)
                break;
        }
        if (check != sum) { // Resort to locking all segments
            sum = 0;
            for (int i = 0; i < segments.length; ++i)
                segments[i].lock();
            for (int i = 0; i < segments.length; ++i)
                sum += segments[i].count;
            for (int i = 0; i < segments.length; ++i)
                segments[i].unlock();
        }
        if (sum > Integer.MAX_VALUE)
            return Integer.MAX_VALUE;
        else
            return (int)sum;
    }
    

    5 总结


    • ConcurrentHashMap 不同于使用同步包装器包装的 HashMap(Collections.synchronizedMap(new HashMap()) 或者 HashTable 等使用全局锁来进行并发访问的控制,其使用了分段锁机制实现了多个线程同时写不会等待。
    • ConcurrentHashMap 利用了 volatile 变量的内存可见性,以及 HashEntery 链表的不变性让读操作可以不进行加锁。

    6 应用


    自己模拟JDK1.6中ConcurrentHashMap中的实现写了一下,有些方法还是不明白,就直接复制的JDK中的源码,如entrySet()方法的实现。

      1 class ConcurrentHashMap<K, V> extends AbstractMap<K, V> {
      2 
      3     // ------------------------------Construct method
      4     @SuppressWarnings("unchecked")
      5     public ConcurrentHashMap() {
      6         this.segments = new Segment[DEFAULT_CONCURRENCY_LEVEL];
      7         for (int i = 0; i < segments.length; i++) {
      8             segments[i] = new Segment<>(DEFAULT_INITIAL_CAPACITY);
      9         }
     10     }
     11 
     12     // -----------------------------Field
     13     /**
     14      * 散列映射表的默认初始容量为 16,即初始默认为16个桶
     15      * 在构造函数中没有指定这个参数时,使用本参数
     16      */
     17     static final int DEFAULT_INITIAL_CAPACITY = 16;
     18 
     19     /**
     20      * 散列表的默认并发级别为 16。该值表示当前更新线程的估计数
     21      * 在构造函数中没有指定这个参数时,使用本参数
     22      */
     23     static final int DEFAULT_CONCURRENCY_LEVEL = 16;
     24 
     25     static final int SEGMENT_SHIFT = 28;
     26 
     27     static final int SEGMENT_MASK = 15;
     28 
     29 
     30 
     31     /**
     32      * 由Segment对象组成的数组
     33      */
     34     final Segment<K, V>[] segments;
     35 
     36     transient Set<Entry<K,V>> entrySet;
     37 
     38     // --------------------------Some util methods
     39     /**
     40      * 哈希再散列,减少哈希冲突:JDK1.6算法
     41      * @param h key的哈希值
     42      * @return 再散列后的哈希值
     43      */
     44     private int hash(int h) {
     45         h += (h <<  15) ^ 0xffffcd7d;
     46         h ^= (h >>> 10);
     47         h += (h <<   3);
     48         h ^= (h >>>  6);
     49         h += (h <<   2) + (h << 14);
     50         return h ^ (h >>> 16);
     51     }
     52 
     53     /**
     54      * 通过key散列后的哈希值来得到segments数组中对应的 Segment
     55      * @param hash key再散列后的哈希值
     56      * @return Segment segment
     57      */
     58     private Segment<K,V> segmentFor(int hash) {
     59         return segments[(hash >>> SEGMENT_SHIFT) & SEGMENT_MASK];
     60     }
     61 
     62     // -------------------------------Some public methods
     63     @Override
     64     public V get(Object key) {
     65         if (key == null) {
     66             throw new NullPointerException();
     67         }
     68         int hash = hash(key.hashCode());
     69         return segmentFor(hash).get(key, hash);
     70     }
     71 
     72     @Override
     73     public V put(K key, V value) {
     74         if (key == null || value == null) {
     75             throw new NullPointerException();
     76         }
     77         int hash = hash(key.hashCode());
     78         return segmentFor(hash).put(key, value, hash);
     79     }
     80 
     81     @Override
     82     public boolean remove(Object key, Object value) {
     83         if (key == null || value == null) {
     84             throw new NullPointerException();
     85         }
     86         int hash = hash(key.hashCode());
     87         return segmentFor(hash).remove(key, value, hash) != null;
     88     }
     89 
     90     @Override
     91     public V remove(Object key) {
     92         if (key == null) {
     93             throw new NullPointerException();
     94         }
     95         int hash = hash(key.hashCode());
     96         V value = segmentFor(hash).get(key, hash);
     97         return segmentFor(hash).remove(key, value, hash);
     98     }
     99 
    100     @Override
    101     public int size() {
    102         final Segment<K,V>[] segments = this.segments;
    103         long sum = 0;
    104         long check = 0;
    105         int[] mc = new int[segments.length];
    106         // Try a few times to get accurate count. On failure due to
    107         // continuous async changes in table, resort to locking.
    108         for (int k = 0; k < 2; ++k) {
    109             check = 0;
    110             sum = 0;
    111             int mcsum = 0;
    112             for (int i = 0; i < segments.length; ++i) {
    113                 sum += segments[i].count;
    114                 mcsum += mc[i] = segments[i].modCount;
    115             }
    116             if (mcsum != 0) {
    117                 for (int i = 0; i < segments.length; ++i) {
    118                     check += segments[i].count;
    119                     if (mc[i] != segments[i].modCount) {
    120                         check = -1; // force retry
    121                         break;
    122                     }
    123                 }
    124             }
    125             if (check == sum)
    126                 break;
    127         }
    128         if (check != sum) { // Resort to locking all segments
    129             sum = 0;
    130             for (int i = 0; i < segments.length; ++i)
    131                 segments[i].lock();
    132             for (int i = 0; i < segments.length; ++i)
    133                 sum += segments[i].count;
    134             for (int i = 0; i < segments.length; ++i)
    135                 segments[i].unlock();
    136         }
    137         if (sum > Integer.MAX_VALUE)
    138             return Integer.MAX_VALUE;
    139         else
    140             return (int)sum;
    141     }
    142 
    143     @Override
    144     public void clear() {
    145         for (int i = 0; i < segments.length; i++)
    146             segments[i].clear();
    147     }
    148 
    149     @Override
    150     public Set<Entry<K, V>> entrySet() {
    151         Set<Entry<K,V>> es = entrySet;
    152         return (es != null) ? es : (entrySet = new EntrySet());
    153     }
    154 
    155 
    156     // ----------------------------Inner class
    157     static class Segment<K, V> extends ReentrantLock implements Serializable {
    158         // --------------------Construct method
    159         @SuppressWarnings("unchecked")
    160         Segment(int initialCapacity) {
    161             table = new Node[initialCapacity];
    162         }
    163         // ---------------------Field
    164         private static final long serialVersionUID = 7249069246763182397L;
    165         // 该Segment中Node的数目
    166         volatile int count;
    167         // 该Segment中Node[]结构的变化次数
    168         int modCount;
    169         // 存放Node的数组
    170         volatile Node<K, V>[] table;
    171 
    172 
    173         // ----------------------Some methods
    174         /**
    175          * 根据key再散列后的哈希值,返回segment中第一个链表节点
    176          * @param hash key再散列后的哈希值
    177          * @return 返回segment中第一个链表节点
    178          */
    179         Node<K, V> getFirst(int hash) {
    180             return table[hash & (table.length - 1)];
    181         }
    182 
    183         V get(Object key, int hash) {
    184             // 读volatile变量,获取主内存中共享变量
    185             if (count != 0) {
    186                 Node<K, V> node = getFirst(hash);
    187                 while (node != null) {
    188                     if (key.equals(node.key) && node.hash == hash) {
    189                         V value = node.getValue();
    190                         if (value != null) {
    191                             return value;
    192                         }
    193                         // 如果value为null,说明发生了重排序,加锁后重读
    194                         return readValueUnderLock(node); // recheck
    195                     }
    196                     node = node.next;
    197                 }
    198                 return null;
    199             }
    200             return null;
    201         }
    202 
    203         V readValueUnderLock(Node<K,V> e) {
    204             lock();             // 获取锁
    205             try {
    206                 return e.value;
    207             } finally {
    208                 unlock();       // 释放锁
    209             }
    210         }
    211 
    212         V put(K key, V value, int hash) {
    213             lock();             // 获取锁
    214             try {
    215                 // 这里c是用来验证是否超过容量的,我没有写扩容机制。
    216                 // 虽然看起来这里如果没有扩容机制的话,就可以不使用本地变量c,
    217                 // 但是这里必须使用本地变量把count的值加一,不能直接count++,
    218                 // 因为只有单个volatile变量的写才有原子性,如果是volatile++,则不具有原子性。
    219                 // 而且我们要先读volatile变量才能获取主内存中的共享变量。
    220                 int c = count;
    221                 c++;
    222                 Node<K, V> node = getFirst(hash);
    223                 Node<K, V> e = null;
    224                 while (node != null) {
    225                     if (key.equals(node.key) && value.equals(node.value) && hash == node.hash) {
    226                         e = node;
    227                     }
    228                     node = node.next;
    229                 }
    230                 // 如果该key、value存在,则修改后返回原值,且结果没有变化。
    231                 if (e != null) {
    232                     V oldValue = e.value;
    233                     e.value = value;
    234                     return oldValue;
    235                 }
    236                 // 该key、value不存在,则添加一个新节点添加到链表头:
    237                 // 这样的话原链表结构不会发生变化,使得其他读线程正常遍历这个链表。
    238                 Node<K, V> first = getFirst(hash);
    239                 e = new Node<>(hash, first, key, value);
    240                 table[hash & (table.length - 1)] = e;
    241                 // 因为添加新节点了,所以modCount要加1
    242                 modCount++;
    243                 // count数量加1:写volatile变量使得该修改对所有读都有效。
    244                 count = c;
    245                 return value;
    246             } finally {
    247                 unlock();       // 释放锁
    248             }
    249         }
    250 
    251         V remove(Object key, Object value, int hash) {
    252             lock();     // 获取锁
    253             try {
    254                 // 与put方法中意义相似,就解释了
    255                 int c = count;
    256                 c--;
    257                 Node<K, V> node = getFirst(hash);
    258                 Node<K, V> e = null;
    259                 while (node != null) {
    260                     if (node.hash == hash && node.key.equals(key) && node.value.equals(value)) {
    261                         e = node;
    262                     }
    263                     node = node.next;
    264                 }
    265                 // 该key、value不存在,返回null
    266                 if (e == null) {
    267                     return null;
    268                 }
    269                 // 该key、value存在,需要删除一个节点
    270                 // 这里的删除没有真正意义上的删除,它新建了一个链表
    271                 // 使得原链表结果没有发生变化,其他读线程正常遍历这个链表
    272                 V oldValue = e.value;
    273                 Node<K, V> newFirst = e.next;
    274                 Node<K, V> first = getFirst(hash);
    275                 while (first != e) {
    276                     newFirst = new Node<>(first.hash, newFirst, first.key, first.value);
    277                     first = first.next;
    278                 }
    279                 table[hash & (table.length - 1)] = newFirst;
    280                 // 因为删除了一个节点,所以modCount要加1
    281                 modCount++;
    282                 // 写volatile变量使得该修改对所有读都有效。
    283                 count = c;
    284                 return oldValue;
    285             } finally {
    286                 unlock();           // 释放锁
    287             }
    288         }
    289 
    290         void clear() {
    291             if (count != 0) {
    292                 lock();             // 获取锁
    293                 try {
    294                     Node<K,V>[] tab = table;
    295                     for (int i = 0; i < tab.length ; i++)
    296                         tab[i] = null;
    297                     // 因为删除了一个节点,所以modCount要加1
    298                     ++modCount;
    299                     // 写volatile变量使得该修改对所有读都有效。
    300                     count = 0;
    301                 } finally {
    302                     unlock();       // 释放锁
    303                 }
    304             }
    305         }
    306     }
    307 
    308     static class Node<K, V> implements Entry<K, V> {
    309         final int hash;
    310         final Node<K, V> next;
    311         final K key;
    312         volatile V value;
    313 
    314         public Node(int hash, Node<K, V> next, K key, V value) {
    315             this.hash = hash;
    316             this.next = next;
    317             this.key = key;
    318             this.value = value;
    319         }
    320 
    321         @Override
    322         public K getKey() {
    323             return key;
    324         }
    325 
    326         @Override
    327         public V getValue() {
    328             return value;
    329         }
    330 
    331         @Override
    332         public V setValue(V value) {
    333             V oldValue = this.value;
    334             this.value = value;
    335             return oldValue;
    336         }
    337     }
    338 
    339 
    340 
    341     final class EntrySet extends AbstractSet<Entry<K,V>> {
    342         public Iterator<Entry<K,V>> iterator() {
    343             return new EntryIterator();
    344         }
    345         public boolean contains(Object o) {
    346             if (!(o instanceof Map.Entry))
    347                 return false;
    348             Entry<?,?> e = (Entry<?,?>)o;
    349             V v = ConcurrentHashMap.this.get(e.getKey());
    350             return v != null && v.equals(e.getValue());
    351         }
    352         public boolean remove(Object o) {
    353             if (!(o instanceof Map.Entry))
    354                 return false;
    355             Entry<?,?> e = (Entry<?,?>)o;
    356             return ConcurrentHashMap.this.remove(e.getKey(), e.getValue());
    357         }
    358         public int size() {
    359             return ConcurrentHashMap.this.size();
    360         }
    361         public void clear() {
    362             ConcurrentHashMap.this.clear();
    363         }
    364     }
    365 
    366     final class EntryIterator extends HashIterator implements Iterator<Entry<K,V>> {
    367         public Entry<K,V> next() {
    368             Node<K,V> e = super.nextEntry();
    369             return new WriteThroughEntry(e.key, e.value);
    370         }
    371     }
    372 
    373     final class WriteThroughEntry
    374             extends SimpleEntry<K,V>
    375     {
    376         WriteThroughEntry(K k, V v) {
    377             super(k,v);
    378         }
    379 
    380         /**
    381          * Set our entry's value and write through to the map. The
    382          * value to return is somewhat arbitrary here. Since a
    383          * WriteThroughEntry does not necessarily track asynchronous
    384          * changes, the most recent "previous" value could be
    385          * different from what we return (or could even have been
    386          * removed in which case the put will re-establish). We do not
    387          * and cannot guarantee more.
    388          */
    389         public V setValue(V value) {
    390             if (value == null) throw new NullPointerException();
    391             V v = super.setValue(value);
    392             ConcurrentHashMap.this.put(getKey(), value);
    393             return v;
    394         }
    395     }
    396 
    397     /* ---------------- Iterator Support -------------- */
    398 
    399     abstract class HashIterator {
    400         int nextSegmentIndex;
    401         int nextTableIndex;
    402         Node<K,V>[] currentTable;
    403         Node<K, V> nextEntry;
    404         Node<K, V> lastReturned;
    405 
    406         HashIterator() {
    407             nextSegmentIndex = segments.length - 1;
    408             nextTableIndex = -1;
    409             advance();
    410         }
    411 
    412         public boolean hasMoreElements() { return hasNext(); }
    413 
    414         final void advance() {
    415             if (nextEntry != null && (nextEntry = nextEntry.next) != null)
    416                 return;
    417 
    418             while (nextTableIndex >= 0) {
    419                 if ( (nextEntry = currentTable[nextTableIndex--]) != null)
    420                     return;
    421             }
    422 
    423             while (nextSegmentIndex >= 0) {
    424                 Segment<K,V> seg = segments[nextSegmentIndex--];
    425                 if (seg.count != 0) {
    426                     currentTable = seg.table;
    427                     for (int j = currentTable.length - 1; j >= 0; --j) {
    428                         if ( (nextEntry = currentTable[j]) != null) {
    429                             nextTableIndex = j - 1;
    430                             return;
    431                         }
    432                     }
    433                 }
    434             }
    435         }
    436 
    437         public boolean hasNext() { return nextEntry != null; }
    438 
    439         Node<K,V> nextEntry() {
    440             if (nextEntry == null)
    441                 throw new NoSuchElementException();
    442             lastReturned = nextEntry;
    443             advance();
    444             return lastReturned;
    445         }
    446 
    447         public void remove() {
    448             if (lastReturned == null)
    449                 throw new IllegalStateException();
    450             ConcurrentHashMap.this.remove(lastReturned.key);
    451             lastReturned = null;
    452         }
    453     }
    454 }
    View Code

    7 Reference


    探索 ConcurrentHashMap 高并发性的实现机制
    《Java并发编程的艺术》

  • 相关阅读:
    Django Rest framework FilterSet 设置 help_text
    树莓派:灯光,相机,动作,和非现场存储
    CFileDialogST v1.0
    使任何应用程序透明的Windows 2000/XP
    产生半透明的对话框和窗口没有闪烁
    一个酷的皮肤GUI与阴影边界
    使用图像蒙皮的表单
    一个控制皮肤组织的控制
    写一个潦草的应用程序使用可视化组件框架
    CRegionCreator
  • 原文地址:https://www.cnblogs.com/maying3010/p/6924768.html
Copyright © 2011-2022 走看看