ConcurrentHashMap详解

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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是有Segment数组组成,每个Segment由HashEntry数组组成,每个HashEntry数组中存放的是HashEntry链表。其中Segment是可重入锁ReentrantLock,它代表锁结构,每个Segment守护它自己包含的HashEntry数组中的元素,这样就体现出锁分段技术。我们在并发访问时,该Segment锁仅仅锁住它自己的一部分数据,从而不会影响其他部分的数据的读写。其结构图如下:
技术分享

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 类型,不能直接删除。它首先找到要删除的那个节点,然后把删除那个节点之前的没有节点中的值复制到新节点中,最后构成一个新链条。具体如下:
执行删除之前的原链表:
技术分享
执行删除之后的新链表:
技术分享

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

6 Reference

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
















以上是关于ConcurrentHashMap详解的主要内容,如果未能解决你的问题,请参考以下文章

Java8 ConcurrentHashMap详解

ConcurrentHashMap原理详解(太细了)

JDK源码分析(12)之 ConcurrentHashMap 详解

Java7 ConcurrentHashMap详解

大长文详解HashMap和ConcurrentHashMap

详解ConcurrentHashMap及JDK8的优化