ConcurrentHashMap源码分析

前言

ConcurrentHashMap是java.util.concurrent包下的一个类,它设计出来是用来在某些情况下替换Hashtable的。相比Hashtable它能够更加高效的进行多线程操作,并不一定需要像Hashtable一样,当一个线程占有锁的时候其他的线程都必须进入阻塞状态,因此在多线程环境下它更加的高效。至于,ConcurrentHashMap是否能够完全替代Hashtable这个问题,博文中有分析到:https://my.oschina.net/hosee/blog/675423。但是,同时它也降低了对数据一致性的要求。在这里额外提一下,java.util.concurrent包中的并发容器,设计出来是用来替换同步容器(多线程环境下,一个线程占有锁的时候其他线程必需进入等待状态,比如Hashtable,Vector),以提供更加高效的并发。

本文将基于JDK1.7的源码进行分析,JDK1.8之后再写。

设计思路

CocurrentHashMap使用分段锁(segment)的方式来减少锁的粒度,它有一个Segment[]属性。不同的线程访问不同segment里面的数据不会产生阻塞,只有多个线程访问同一个segment才会产生锁的竞争。segment中有一个HashEntry数组,同时它还继承了ReentrantLock,所以它就类似于一个Hashtable。

/**
 * The segments, each of which is a specialized hash table.
 */
final Segment<K,V>[] segments;

static final class Segment<K,V> extends ReentrantLock implements Serializable
    transient volatile HashEntry<K,V>[] table;

static final class HashEntry<K,V> {
        final int hash;
        final K key;
        volatile V value;
        volatile HashEntry<K,V> next;
}

HashEntry节点和HashMap中的Entry稍有不同的就是,value和next节点都有volatile关键词修饰,这个是为了保证多线程环境下的可见性。ConcurrentHashMap的结构图大致如下:

绘图2

Hash算法

无论是HashMap还是ConcurrentHashMap的基础都是hash算法,下面是它的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);
} 

public V put(K key, V value) {
    Segment<K,V> s;
    if (value == null)
        throw new NullPointerException();
    int hash = hash(key);
    int j = (hash >>> segmentShift) & segmentMask;
    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);
}

可能有人会有疑问,为什么能够直接通过Object的hashcode()方法得到hashcode的值之后还需要再单独定义一个hash()方法进行再哈希?因为直接计算索引受限于segments.length转换为2进制后的有效位数。具体看下面这个例子体会一下:

对于segments.length == 16的ConcurrentHashMap,(segments.length - 1) == 15转换为2进制数后为1111,有效位数为4位。现有4个key,hascode的值分别为:15,31,63,127,它们转换为2进制数后对应的值分别为:1111,11111,111111,111111。现在对它们求索引:

15 & 15 ==> 1111 & 1111 = 1111

31 & 15 ==> 11111 & 01111 = 01111

63 & 15 ==> 111111 & 001111 = 001111

127 & 15 ==> 1111111 & 0001111 = 0001111

最终求出索引的值转换为10进制数后都是15,可以看出直接用hascode的值求索引,受限于length的转换为2进制的有效位数,比较容易产生hash冲突。为了解决这个问题,就需要利用key的hascode转换为2进制的后的有效位数的不同,进行再hash运算,最终使得进行&运算的时候有效位数不同。仍然是上面的这个例子,看一下通过Wang/Jenkins hash算法之后,为了方便阅读将数据转换为32位的2进制数据,不足位用0补齐:

0100 0111 0110 0111 1101 1010 0100 1110
1111 0111 0100 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ConcurrentHashMap是Java中的一个并发容器,用于在多线程环境中安全地存储和访问键值对。它使用了一些特殊的技术来提高其并发性能。 ConcurrentHashMap源码分析可以从几个关键点开始。首先,它使用了大量的CAS(Compare and Swap)操作来代替传统的重量级锁操作,从而提高了并发性能。只有在节点实际变动的过程中才会进行加锁操作,这样可以减少对整个容器的锁竞争。 其次,ConcurrentHashMap的数据结构是由多个Segment组成的,每个Segment又包含多个HashEntry。这样的设计使得在多线程环境下,不同的线程可以同时对不同的Segment进行操作,从而提高了并发性能。每个Segment都相当于一个独立的HashMap,有自己的锁来保证线程安全。 在JDK1.7版本中,ConcurrentHashMap采用了分段锁的设计,即每个Segment都有自己的锁。这样的设计可以在多线程环境下提供更好的并发性能,因为不同的线程可以同时对不同的Segment进行读写操作,从而减少了锁竞争。 总的来说,ConcurrentHashMap通过使用CAS操作、分段锁以及特定的数据结构来实现线程安全的并发访问。这使得它成为在多线程环境中高效地存储和访问键值对的选择。123 #### 引用[.reference_title] - *1* [ConcurrentHashMap 源码解析](https://blog.csdn.net/Vampirelzl/article/details/126548972)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}} ] [.reference_item] - *2* *3* [ConcurrentHashMap源码分析](https://blog.csdn.net/java123456111/article/details/124883950)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值