JDK1.7的ConcurrentHashMap 的put、get、remove工作原理

很详细,很不错,好理解,具体请看

ConcurrentHashMap使用了分段锁技术,每个Segment都是一把锁,本身Segment继承了ReentranLock

ConcurrentHashMap由 Segment 数组、每个Segment包含一个HashEntry 数组(HashEntry<K,V>[] table),为一个小的hash表。 Segment和 HashMap 一样,仍然是数组加链表。

  • put总结:

put只对对应的Segment加锁,其他的Segment不受影响,提高并发度。

就是说如果容量大小是16他的并发度就是16,可以同时允许16个线程操作16个Segment而且还是线程安全的。

1)根据散列码hash( 调用segmentFor(hash) )找到对应的 Segment

2)在这个 Segment 中执行具体的真正的 put 操作(segment.put( key, hash, value, false) )

3)加锁。对该Segment 对象而不是整个concurrentHashMap加锁

4)得到该散列码对应的 table 数组的下标值index,并找到散列码对应的具体的那个桶 tab[index]

5)while寻找,若键 / 值对已存在,覆盖新值返回旧值; 键 / 值对不存在 ,创建新节点,并添加到链表的头部 ,返回 null

6)解锁该Segment

 

//并非完整源码,只是关键部分
public V put(K key, V value) { 
       if (value == null)          //ConcurrentHashMap 中不允许用 null 作为映射值
           throw new NullPointerException(); 
       int hash = hash(key.hashCode());        // 计算键对应的散列码
       // 根据散列码找到对应的 Segment 
       return segmentFor(hash).put(key, hash, value, false); 
    }
     根据 hash 值找到对应的 Segment:
    /** 
    * 使用 key 的散列码来得到 segments 数组中对应的 Segment 
    */ 
    final Segment<K,V> segmentFor(int hash) { 
    // 将散列值右移 segmentShift 个位,并在高位填充 0 
    // 然后把得到的值与 segmentMask 相“与”
    // 从而得到 hash 值对应的 segments 数组的下标值
    // 最后根据下标值返回散列码对应的 Segment 对象
        return segments[(hash >>> segmentShift) & segmentMask]; 
    }

V put(K key, int hash, V value, boolean onlyIfAbsent) { 
           lock();  // 加锁,这里是锁定某个 Segment 对象而非整个 ConcurrentHashMap 
           try { 
               int c = count; 
 
               if (c++ > threshold)     // 如果超过再散列的阈值
                   rehash();              // 执行再散列,table 数组的长度将扩充一倍
 
               HashEntry<K,V>[] tab = table; 
               // 把散列码值与 table 数组的长度减 1 的值相“与”
               // 得到该散列码对应的 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; 
               if (e != null) {            // 如果键 / 值对以经存在
                   oldValue = e.value; 
                   if (!onlyIfAbsent) 
                       e.value = value;    // 设置 value 值
               } 
               else {                        // 键 / 值对不存在 
                   oldValue = null; 
                   ++modCount;         // 要添加新节点到链表中,所以 modCont 要加 1  
                   // 创建新节点,并添加到链表的头部 
                   tab[index] = new HashEntry<K,V>(key, hash, first, value); 
                   count = c;               // 写 count 变量
               } 
               return oldValue; 
           } finally { 
               unlock();                     // 解锁
           } 
       `}
  • get总结:

在该Segment内部get。get全程不加锁,并发度高。

get操作可以无锁是由于HashEntry的元素val和指针next是用volatile修饰的,在多线程环境下线程A修改结点的val或者新增节点的时候是对线程B可见的。

1)key 通过 hash 之后定位到具体的 Segment,再进行get操作。

2)count不为0,使用hash得到对应桶的第一个HashEntry

注:count为在本 segment 范围内,包含的 HashEntry 元素的个数。该变量被声明为 volatile 型,保证每次读取到最新的数据

3)while循环依次遍历,使用if(e.hash == hash && key.equals(e.key)) 寻找目标值。

V get(Object key, int hash) { 
        if(count != 0) {       // 首先读 count 变量
            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; 
                    // 如果读到 value 域为 null,说明发生了重排序,加锁后重新读取
                    return readValueUnderLock(e); 
                } 
                e = e.next; 
            } 
        } 
        return null; 
    }
     V readValueUnderLock(HashEntry<K,V> e) {  
         lock();  
         try {  
             return e.value;  
         } finally {  
             unlock();  
         }  
     }

是的,因为基本上还是数组加链表的方式,我们去查询的时候,还得遍历链表,会导致效率很低,这个跟jdk1.7的HashMap是存在的一样问题,所以他在jdk1.8完全优化了。其中抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。


 

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值