ConcurrentHashMap源代码解读

JDK1.7.0_40

下面从ConcurrentHashMap的创建,获取,添加,删除这4个方面对ConcurrentHashMap进行分析。

1 创建

下面以ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel)来进行说明。

复制代码
@SuppressWarnings("unchecked")
public ConcurrentHashMap(int initialCapacity,
                         float loadFactor, int concurrencyLevel) {
    // 参数有效性判断
    if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
        throw new IllegalArgumentException();
    // concurrencyLevel是“用来计算segments的容量”
    if (concurrencyLevel > MAX_SEGMENTS)
        concurrencyLevel = MAX_SEGMENTS;
    int sshift = 0;
    int ssize = 1;
    // ssize=“大于或等于concurrencyLevel的最小的2的N次方值”
    while (ssize < concurrencyLevel) {
        ++sshift;
        ssize <<= 1;
    }
    // 初始化segmentShift和segmentMask
    this.segmentShift = 32 - sshift;
    this.segmentMask = ssize - 1;
    // 哈希表的初始容量
    // 哈希表的实际容量=“segments的容量” x “segments中数组的长度”
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    // “哈希表的初始容量” / “segments的容量”
    int c = initialCapacity / ssize;
    if (c * ssize < initialCapacity)
        ++c;
    // cap就是“segments中的HashEntry数组的长度”
    int cap = MIN_SEGMENT_TABLE_CAPACITY;
    while (cap < c)
        cap <<= 1;
    // segments
    Segment<K,V> s0 =
        new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
                         (HashEntry<K,V>[])new HashEntry[cap]);
    Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
    UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
    this.segments = ss;
}
复制代码

说明
(01) 前面我们说过,ConcurrentHashMap采用了“锁分段”技术;在代码中,它通过“segments数组”对象来保存各个分段。segments的定义如下:

final Segment<K,V>[] segments;

    concurrencyLevel的作用就是用来计算segments数组的容量大小。先计算出“大于或等于concurrencyLevel的最小的2的N次方值”,然后将其保存为“segments的容量大小(ssize)”。
(02) initialCapacity是哈希表的初始容量。需要注意的是,哈希表的实际容量=“segments的容量” x “segments中数组的长度”。
(03) loadFactor是加载因子。它是哈希表在其容量自动增加之前可以达到多满的一种尺度。


ConcurrentHashMap的构造函数中涉及到的非常重要的一个结构体,它就是Segment。下面看看Segment的声明:

复制代码
static final class Segment<K,V> extends ReentrantLock implements Serializable {
    ...

    transient volatile HashEntry<K,V>[] table;
    // threshold阈,是哈希表在其容量自动增加之前可以达到多满的一种尺度。
    transient int threshold;
    // loadFactor是加载因子
    final float loadFactor;

    Segment(float lf, int threshold, HashEntry<K,V>[] tab) {
        this.loadFactor = lf;
        this.threshold = threshold;
        this.table = tab;
    }

    ...
}
复制代码

说明:Segment包含HashEntry数组,HashEntry保存了哈希表中的键值对。
此外,还需要说明的Segment继承于ReentrantLock。这意味着,Segment本质上就是可重入的互斥锁。

HashEntry的源码如下:

复制代码
static final class HashEntry<K,V> {
    final int hash;    // 哈希值
    final K key;       //
    volatile V value;  //
    volatile HashEntry<K,V> next; // 下一个HashEntry节点

    HashEntry(int hash, K key, V value, HashEntry<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

    ...
}
复制代码

说明:和HashMap的节点一样,HashEntry也是链表。这就说明,ConcurrentHashMap是链式哈希表,它是通过“拉链法”来解决哈希冲突的。

 

2 获取

下面以get(Object key)为例,对ConcurrentHashMap的获取方法进行说明。

复制代码
public V get(Object key) {
    Segment<K,V> s; // manually integrate access methods to reduce overhead
    HashEntry<K,V>[] tab;
    int h = hash(key);
    long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
    // 获取key对应的Segment片段。
    // 如果Segment片段不为null,则在“Segment片段的HashEntry数组中”中找到key所对应的HashEntry列表;
    // 接着遍历该HashEntry链表,找到于key-value键值对对应的HashEntry节点。
    if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
        (tab = s.table) != null) {
        for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
                 (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
             e != null; e = e.next) {
            K k;
            if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                return e.value;
        }
    }
    return null;
}
复制代码

说明:get(Object key)的作用是返回key在ConcurrentHashMap哈希表中对应的值。
它首先根据key计算出来的哈希值,获取key所对应的Segment片段。
如果Segment片段不为null,则在“Segment片段的HashEntry数组中”中找到key所对应的HashEntry列表。Segment包含“HashEntry数组”对象,而每一个HashEntry本质上是一个单向链表。
接着遍历该HashEntry链表,找到于key-value键值对对应的HashEntry节点。

下面是hash()的源码

复制代码
private int hash(Object k) {
    int h = hashSeed;

    if ((0 != h) && (k instanceof String)) {
        return sun.misc.Hashing.stringHash32((String) k);
    }

    h ^= k.hashCode();

    // 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);
}
复制代码

 

3 增加

下面以put(K key, V value)来对ConcurrentHashMap中增加键值对来进行说明。

复制代码
public V put(K key, V value) {
    Segment<K,V> s;
    if (value == null)
        throw new NullPointerException();
    // 获取key对应的哈希值
    int hash = hash(key);
    int j = (hash >>> segmentShift) & segmentMask;
    // 如果找不到该Segment,则新建一个。
    if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
         (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
        s = ensureSegment(j);
    return s.put(key, hash, value, false);
}
复制代码

说明
(01) put()根据key获取对应的哈希值,再根据哈希值找到对应的Segment片段。如果Segment片段不存在,则新增一个Segment。
(02) 将key-value键值对添加到Segment片段中。

复制代码
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
    // tryLock()获取锁,成功返回true,失败返回false。
    // 获取锁失败的话,则通过scanAndLockForPut()获取锁,并返回”要插入的key-value“对应的”HashEntry链表“。
    HashEntry<K,V> node = tryLock() ? null :
        scanAndLockForPut(key, hash, value);
    V oldValue;
    try {
        // tab代表”当前Segment中的HashEntry数组“
        HashEntry<K,V>[] tab = table;
        //  根据”hash值“获取”HashEntry数组中对应的HashEntry链表“
        int index = (tab.length - 1) & hash;
        HashEntry<K,V> first = entryAt(tab, index);
        for (HashEntry<K,V> e = first;;) {
            // 如果”HashEntry链表中的当前HashEntry节点“不为null,
            if (e != null) {
                K k;
                // 当”要插入的key-value键值对“已经存在于”HashEntry链表中“时,先保存原有的值。
                // 若”onlyIfAbsent“为true,即”要插入的key不存在时才插入”,则直接退出;
                // 否则,用新的value值覆盖原有的原有的值。
                if ((k = e.key) == key ||
                    (e.hash == hash && key.equals(k))) {
                    oldValue = e.value;
                    if (!onlyIfAbsent) {
                        e.value = value;
                        ++modCount;
                    }
                    break;
                }
                e = e.next;
            }
            else {
                // 如果node非空,则将first设置为“node的下一个节点”。
                // 否则,新建HashEntry链表
                if (node != null)
                    node.setNext(first);
                else
                    node = new HashEntry<K,V>(hash, key, value, first);
                int c = count + 1;
                // 如果添加key-value键值对之后,Segment中的元素超过阈值(并且,HashEntry数组的长度没超过限制),则rehash;
                // 否则,直接添加key-value键值对。
                if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                    rehash(node);
                else
                    setEntryAt(tab, index, node);
                ++modCount;
                count = c;
                oldValue = null;
                break;
            }
        }
    } finally {
        // 释放锁
        unlock();
    }
    return oldValue;
}
复制代码

说明
put()的作用是将key-value键值对插入到“当前Segment对应的HashEntry中”,在插入前它会获取Segment对应的互斥锁,插入后会释放锁。具体的插入过程如下:
(01) 首先根据“hash值”获取“当前Segment的HashEntry数组对象”中的“HashEntry节点”,每个HashEntry节点都是一个单向链表。
(02) 接着,遍历HashEntry链表。
       若在遍历HashEntry链表时,找到与“要key-value键值对”对应的节点,即“要插入的key-value键值对”的key已经存在于HashEntry链表中。则根据onlyIfAbsent进行判断,若onlyIfAbsent为true,即“当要插入的key不存在时才插入”,则不进行插入,直接返回;否则,用新的value值覆盖原始的value值,然后再返回。
       若在遍历HashEntry链表时,没有找到与“要key-value键值对”对应的节点。当node!=null时,即在scanAndLockForPut()获取锁时,已经新建了key-value对应的HashEntry节点,则”将HashEntry添加到Segment中“;否则,新建key-value对应的HashEntry节点,然后再“将HashEntry添加到Segment中”。 在”将HashEntry添加到Segment中“前,会判断是否需要rehash。如果在添加key-value键值之后,容量会超过阈值,并且HashEntry数组的长度没有超过限制,则进行rehash;否则,直接通过setEntryAt()将key-value键值对添加到Segment中。

在介绍rehash()和setEntryAt()之前,我们先看看自旋函数scanAndLockForPut()。下面是它的源码:

复制代码
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
    // 第一个HashEntry节点
    HashEntry<K,V> first = entryForHash(this, hash);
    // 当前的HashEntry节点
    HashEntry<K,V> e = first;
    HashEntry<K,V> node = null;
    // 重复计数(自旋计数器)
    int retries = -1; // negative while locating node

    // 查找”key-value键值对“在”HashEntry链表上对应的节点“;
    // 若找到的话,则不断的自旋;在自旋期间,若通过tryLock()获取锁成功则返回;否则自旋MAX_SCAN_RETRIES次数之后,强制获取”锁“并退出。
    // 若没有找到的话,则新建一个HashEntry链表。然后不断的自旋。
    // 此外,若在自旋期间,HashEntry链表的表头发生变化;则重新进行查找和自旋工作!
    while (!tryLock()) {
        HashEntry<K,V> f; // to recheck first below
        // 1. retries<0的处理情况
        if (retries < 0) {
            // 1.1 如果当前的HashEntry节点为空(意味着,在该HashEntry链表上上没有找到”要插入的键值对“对应的节点),而且node=null;则新建HashEntry链表。
            if (e == null) {
                if (node == null) // speculatively create node
                    node = new HashEntry<K,V>(hash, key, value, null);
                retries = 0;
            }
            // 1.2 如果当前的HashEntry节点是”要插入的键值对在该HashEntry上对应的节点“,则设置retries=0
            else if (key.equals(e.key))
                retries = 0;
            // 1.3 设置为下一个HashEntry。
            else
                e = e.next;
        }
        // 2. 如果自旋次数超过限制,则获取“锁”并退出
        else if (++retries > MAX_SCAN_RETRIES) {
            lock();
            break;
        }
        // 3. 当“尝试了偶数次”时,就获取“当前Segment的第一个HashEntry”,即f。
        // 然后,通过f!=first来判断“当前Segment的第一个HashEntry是否发生了改变”。
        // 若是的话,则重置e,first和retries的值,并重新遍历。
        else if ((retries & 1) == 0 &&
                 (f = entryForHash(this, hash)) != first) {
            e = first = f; // re-traverse if entry changed
            retries = -1;
        }
    }
    return node;
}
复制代码

说明
scanAndLockForPut()的目标是获取锁。流程如下:
    它首先会调用entryForHash(),根据hash值获取”当前Segment中对应的HashEntry节点(first),即找到对应的HashEntry链表“。
    紧接着进入while循环。在while循环中,它会遍历”HashEntry链表(e)“,查找”要插入的key-value键值对“在”该HashEntry链表上对应的节点“。
         若找到的话,则不断的自旋,即不断的执行while循环。在自旋期间,若通过tryLock()获取锁成功则返回;否则,在自旋MAX_SCAN_RETRIES次数之后,强制获取锁并退出。
         若没有找到的话,则新建一个HashEntry链表,然后不断的自旋。在自旋期间,若通过tryLock()获取锁成功则返回;否则,在自旋MAX_SCAN_RETRIES次数之后,强制获取锁并退出。
     此外,若在自旋期间,HashEntry链表的表头发生变化;则重新进行查找和自旋工作!

理解scanAndLockForPut()时,务必要联系”哈希表“的数据结构。一个Segment本身就是一个哈希表,Segment中包含了”HashEntry数组“对象,而每一个HashEntry对象本身是一个”单向链表“。

 

下面看看rehash()的实现代码。

复制代码
private void rehash(HashEntry<K,V> node) {
    HashEntry<K,V>[] oldTable = table;
    // ”Segment中原始的HashEntry数组的长度“
    int oldCapacity = oldTable.length;
    // ”Segment中新HashEntry数组的长度“
    int newCapacity = oldCapacity << 1;
    // 新的阈值
    threshold = (int)(newCapacity * loadFactor);
    // 新的HashEntry数组
    HashEntry<K,V>[] newTable =
        (HashEntry<K,V>[]) new HashEntry[newCapacity];
    int sizeMask = newCapacity - 1;
    // 遍历”原始的HashEntry数组“,
    // 将”原始的HashEntry数组“中的每个”HashEntry链表“的值,都复制到”新的HashEntry数组的HashEntry元素“中。
    for (int i = 0; i < oldCapacity ; i++) {
        // 获取”原始的HashEntry数组“中的”第i个HashEntry链表“
        HashEntry<K,V> e = oldTable[i];
        if (e != null) {
            HashEntry<K,V> next = e.next;
            int idx = e.hash & sizeMask;
            if (next == null)   //  Single node on list
                newTable[idx] = e;
            else { // Reuse consecutive sequence at same slot
                HashEntry<K,V> lastRun = e;
                int lastIdx = idx;
                for (HashEntry<K,V> last = next;
                     last != null;
                     last = last.next) {
                    int k = last.hash & sizeMask;
                    if (k != lastIdx) {
                        lastIdx = k;
                        lastRun = last;
                    }
                }
                newTable[lastIdx] = lastRun;
                // 将”原始的HashEntry数组“中的”HashEntry链表(e)“的值,都复制到”新的HashEntry数组的HashEntry“中。
                for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {
                    V v = p.value;
                    int h = p.hash;
                    int k = h & sizeMask;
                    HashEntry<K,V> n = newTable[k];
                    newTable[k] = new HashEntry<K,V>(h, p.key, v, n);
                }
            }
        }
    }
    // 将新的node节点添加到“Segment的新HashEntry数组(newTable)“中。
    int nodeIndex = node.hash & sizeMask; // add the new node
    node.setNext(newTable[nodeIndex]);
    newTable[nodeIndex] = node;
    table = newTable;
}
复制代码

说明:rehash()的作用是将”Segment的容量“变为”原始的Segment容量的2倍“。
在将原始的数据拷贝到“新的Segment”中后,会将新增加的key-value键值对添加到“新的Segment”中。

setEntryAt()的源码如下:

static final <K,V> void setEntryAt(HashEntry<K,V>[] tab, int i,
                                   HashEntry<K,V> e) {
    UNSAFE.putOrderedObject(tab, ((long)i << TSHIFT) + TBASE, e);
}

UNSAFE是Segment类中定义的“静态sun.misc.Unsafe”对象。源码如下:

static final sun.misc.Unsafe UNSAFE;

Unsafe.java在openjdk6中的路径是:openjdk6/jdk/src/share/classes/sun/misc/Unsafe.java。其中,putOrderedObject()的源码下:

public native void putOrderedObject(Object o, long offset, Object x);

说明:putOrderedObject()是一个本地方法。 
它会设置obj对象中offset偏移地址对应的object型field的值为指定值。它是一个有序或者有延迟的putObjectVolatile()方法,并且不保证值的改变被其他线程立即看到。只有在field被volatile修饰并且期望被意外修改的时候,使用putOrderedObject()才有用。

总之,setEntryAt()的目的是设置tab中第i位置元素的值为e,且该设置会有延迟。

 

4 删除

下面以remove(Object key)来对ConcurrentHashMap中的删除操作来进行说明。

public V remove(Object key) {
    int hash = hash(key);
    // 根据hash值,找到key对应的Segment片段。
    Segment<K,V> s = segmentForHash(hash);
    return s == null ? null : s.remove(key, hash, null);
}

说明:remove()首先根据“key的计算出来的哈希值”找到对应的Segment片段,然后再从该Segment片段中删除对应的“key-value键值对”。

remove()的方法如下:

复制代码
final V remove(Object key, int hash, Object value) {
    // 尝试获取Segment对应的锁。
    // 尝试失败的话,则通过scanAndLock()来获取锁。
    if (!tryLock())
        scanAndLock(key, hash);
    V oldValue = null;
    try {
        // 根据“hash值”找到“Segment的HashEntry数组”中对应的“HashEntry节点(e)”,该HashEntry节点是一HashEntry个链表。
        HashEntry<K,V>[] tab = table;
        int index = (tab.length - 1) & hash;
        HashEntry<K,V> e = entryAt(tab, index);
        HashEntry<K,V> pred = null;
        // 遍历“HashEntry链表”,删除key-value键值对
        while (e != null) {
            K k;
            HashEntry<K,V> next = e.next;
            if ((k = e.key) == key ||
                (e.hash == hash && key.equals(k))) {
                V v = e.value;
                if (value == null || value == v || value.equals(v)) {
                    if (pred == null)
                        setEntryAt(tab, index, next);
                    else
                        pred.setNext(next);
                    ++modCount;
                    --count;
                    oldValue = v;
                }
                break;
            }
            pred = e;
            e = next;
        }
    } finally {
        // 释放锁
        unlock();
    }
    return oldValue;
}
复制代码

说明remove()的目的就是删除key-value键值对。在删除之前,它会获取到Segment的互斥锁,在删除之后,再释放锁。
它的删除过程也比较简单,它会先根据hash值,找到“Segment的HashEntry数组”中对应的“HashEntry”节点。根据Segment的数据结构,我们知道Segment中包含一个HashEntry数组对象,而每一个HashEntry本质上是一个单向链表。 在找到“HashEntry”节点之后,就遍历该“HashEntry”节点对应的链表,找到key-value键值对对应的节点,然后删除。

下面对scanAndLock()进行说明。它的源码如下:

复制代码
private void scanAndLock(Object key, int hash) {
    // 第一个HashEntry节点
    HashEntry<K,V> first = entryForHash(this, hash);
    HashEntry<K,V> e = first;
    int retries = -1;

    // 查找”key-value键值对“在”HashEntry链表上对应的节点“;
    // 无论找没找到,最后都会不断的自旋;在自旋期间,若通过tryLock()获取锁成功则返回;否则自旋MAX_SCAN_RETRIES次数之后,强制获取”锁“并退出。
    // 若在自旋期间,HashEntry链表的表头发生变化;则重新进行查找和自旋!
    while (!tryLock()) {
        HashEntry<K,V> f;
        if (retries < 0) {
            // 如果“遍历完该HashEntry链表,仍然没找到”要删除的键值对“对应的节点”
            // 或者“在该HashEntry链表上找到”要删除的键值对“对应的节点”,则设置retries=0
            // 否则,设置e为下一个HashEntry节点。
            if (e == null || key.equals(e.key))
                retries = 0;
            else
                e = e.next;
        }
        // 自旋超过限制次数之后,获取锁并退出。
        else if (++retries > MAX_SCAN_RETRIES) {
            lock();
            break;
        }
        // 当“尝试了偶数次”时,就获取“当前Segment的第一个HashEntry”,即f。
        // 然后,通过f!=first来判断“当前Segment的第一个HashEntry是否发生了改变”。
        // 若是的话,则重置e,first和retries的值,并重新遍历。
        else if ((retries & 1) == 0 &&
                 (f = entryForHash(this, hash)) != first) {
            e = first = f;
            retries = -1;
        }
    }
}
复制代码

说明scanAndLock()的目标是获取锁。它的实现与scanAndLockForPut()类似,这里就不再过多说明。

 

总结ConcurrentHashMap是线程安全的哈希表,它是通过“锁分段”来实现的。ConcurrentHashMap中包括了“Segment(锁分段)数组”,每个Segment就是一个哈希表,而且也是可重入的互斥锁。第一,Segment是哈希表表现在,Segment包含了“HashEntry数组”,而“HashEntry数组”中的每一个HashEntry元素是一个单向链表。即Segment是通过链式哈希表。第二,Segment是可重入的互斥锁表现在,Segment继承于ReentrantLock,而ReentrantLock就是可重入的互斥锁。
对于ConcurrentHashMap的添加,删除操作,在操作开始前,线程都会获取Segment的互斥锁;操作完毕之后,才会释放。而对于读取操作,它是通过volatile去实现的,HashEntry数组是volatile类型的,而volatile能保证“即对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入”,即我们总能读到其它线程写入HashEntry之后的值。 以上这些方式,就是ConcurrentHashMap线程安全的实现原理。

 

ConcurrentHashMap示例

下面,我们通过一个例子去对比HashMap和ConcurrentHashMap。

复制代码
 1 import java.util.*;
 2 import java.util.concurrent.*;
 3 
 4 /*
 5  *   ConcurrentHashMap是“线程安全”的哈希表,而HashMap是非线程安全的。
 6  *
 7  *   下面是“多个线程同时操作并且遍历map”的示例
 8  *   (01) 当map是ConcurrentHashMap对象时,程序能正常运行。
 9  *   (02) 当map是HashMap对象时,程序会产生ConcurrentModificationException异常。
10  *
11  * @author skywang
12  */
13 public class ConcurrentHashMapDemo1 {
14 
15     // TODO: map是HashMap对象时,程序会出错。
16     //private static Map<String, String> map = new HashMap<String, String>();
17     private static Map<String, String> map = new ConcurrentHashMap<String, String>();
18     public static void main(String[] args) {
19     
20         // 同时启动两个线程对map进行操作!
21         new MyThread("ta").start();
22         new MyThread("tb").start();
23     }
24 
25     private static void printAll() {
26         String key, value;
27         Iterator iter = map.entrySet().iterator();
28         while(iter.hasNext()) {
29             Map.Entry entry = (Map.Entry)iter.next();
30             key = (String)entry.getKey();
31             value = (String)entry.getValue();
32             System.out.print(key+" - "+value+", ");
33         }
34         System.out.println();
35     }
36 
37     private static class MyThread extends Thread {
38         MyThread(String name) {
39             super(name);
40         }
41         @Override
42         public void run() {
43                 int i = 0;
44             while (i++ < 6) {
45                 // “线程名” + "-" + "序号"
46                 String val = Thread.currentThread().getName()+i;
47                 map.put(String.valueOf(i), val);
48                 // 通过“Iterator”遍历map。
49                 printAll();
50             }
51         }
52     }
53 }
复制代码

(某一次)运行结果

复制代码
1 - tb1, 
1 - tb1, 
1 - tb1, 1 - tb1, 2 - tb2, 
2 - tb2, 1 - tb1, 
3 - ta3, 1 - tb1, 2 - tb2, 
3 - tb3, 1 - tb1, 2 - tb2, 
3 - tb3, 1 - tb1, 4 - tb4, 3 - tb3, 2 - tb2, 
4 - tb4, 1 - tb1, 2 - tb2, 
5 - ta5, 1 - tb1, 3 - tb3, 5 - tb5, 4 - tb4, 3 - tb3, 2 - tb2, 
4 - tb4, 1 - tb1, 2 - tb2, 
5 - tb5, 1 - tb1, 6 - tb6, 5 - tb5, 3 - tb3, 6 - tb6, 4 - tb4, 3 - tb3, 2 - tb2, 
4 - tb4, 2 - tb2, 
复制代码

结果说明如果将源码中的map改成HashMap对象时,程序会产生ConcurrentModificationException异常。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ConcurrentHashMap 1.8 是 Java 中线程安全的哈希表实现,它是 Java 5 中引入的 ConcurrentHashMap 的升级版本。以下是 ConcurrentHashMap 1.8 的主要代码实现: 1. 分段锁实现 ConcurrentHashMap 1.8 在内部使用了分段锁(Segment)来实现线程安全。每个 Segment 都是一个独立的哈希表,拥有自己的锁,不同线程可以同时访问不同的 Segment,从而提高并发性能。 2. 数据结构 ConcurrentHashMap 1.8 内部的数据结构是一个 Segment 数组,每个 Segment 内部是一个哈希表,哈希表中的每个元素是一个链表或红黑树。 3. put 操作实现 ConcurrentHashMap 1.8 的 put 操作首先会根据 Key 的哈希值和哈希表的长度计算出元素应该存放在哪个 Segment 中,然后在该 Segment 的哈希表中查找元素。 如果元素已经存在,就直接替换其值;否则就创建一个新的节点并添加到链表或红黑树中。如果链表或红黑树的长度超过了一定阈值,就会将链表转化为红黑树,从而提高查找效率。 4. get 操作实现 ConcurrentHashMap 1.8 的 get 操作与 put 操作类似,首先会根据哈希值和哈希表长度计算出元素所在的 Segment,然后在该 Segment 的哈希表中查找元素。 如果元素存在于链表中,就顺序查找;如果存在于红黑树中,就使用红黑树的查找算法。如果找到了元素,就返回其值;否则返回 null。 5. 扩容实现 ConcurrentHashMap 1.8 的扩容过程与 ConcurrentHashMap 1.7 相比,更加高效。它采用了一种“分段锁粒度更细”的方式,只对需要扩容的 Segment 进行加锁,其他 Segment 可以继续访问,从而提高并发性能。 6. 其他实现细节 ConcurrentHashMap 1.8 还实现了一些其他的细节,例如使用 Unsafe 类来实现 CAS 操作、使用 ThreadLocalRandom 来生成哈希值等,从而保证了其高性能和线程安全性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值