ConcurrentHashMap源码分析


ConcurrentHashMap

简介

HashMap: 线程不安全,在并发环境下,扩容时可能会形成环形链表(1.8之前),导致在 get 操作时,cpu空转。

HashTable: 相比 HashMap 不允许 key 和 value 为空,且是线程安全的。但所有的 get、put 方法都加上了 synchronized 锁,相当于给整个哈希表加了一把大锁,当一个线程访问时,其它线程只能等待,耗时太大。

ConcurrentHashMap: 采用分段锁策略。多个线程访问不同的分段时,不存在锁竞争;只有同一分段操作才需要考虑线程同步问题。


源码分析

属性

		transient volatile Node<K,V>[] table;
		final Segment<K,V>[] segments;

主要属性相似于 HashMap ,这里主要分析两个不同的:segments、HashEntry(Segments内部)。

Segments: 继承 ReentrantLock,一个 segements 对应一个子哈希表,Segment 内部维护了一个 HashEntry 数组(类似于HashMap)。

		static final class Segment<K,V> extends ReentrantLock implements Serializable {

		    private static final long serialVersionUID = 2249069246763182397L;
		
		    static final int MAX_SCAN_RETRIES = Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
		
		    transient volatile HashEntry<K,V>[] table;
		
		    transient int count;
		
		    transient int modCount;
		
		    transient int threshold;
		
		    final float loadFactor;
		}

HashEntry: 目前最小的逻辑处理单元。

		static final class HashEntry<K,V> {
	        final int hash;
	        final K key;
	        volatile V value;
	        volatile HashEntry<K,V> next;
	        //其他省略
	}    

构造方法

		//初始化默认参数:initialCapacity为16,loadFactor为0.75,concurrentLevel为16
		public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
        	if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)throw new IllegalArgumentException();
           //MAX_SEGMENTS 为1<<16=65536,也就是最大并发数为65536
           	if (concurrencyLevel > MAX_SEGMENTS)
               concurrencyLevel = MAX_SEGMENTS;
           //2的sshif次方等于ssize,例:ssize=16,sshift=4;ssize=32,sshif=5
	        int sshift = 0;
	        //ssize 为segments数组长度,根据concurrentLevel计算得出
	        int ssize = 1;
	        while (ssize < concurrencyLevel) {
	        	++sshift;
	            ssize <<= 1;
	        }
	        //segmentShift和segmentMask这两个变量用于定位 segment
	        this.segmentShift = 32 - sshift;
	        this.segmentMask = ssize - 1;
	        if (initialCapacity > MAXIMUM_CAPACITY)
	            initialCapacity = MAXIMUM_CAPACITY;
	        //计算cap的大小,即Segment中HashEntry的数组长度,cap也一定为2的n次方.
	        int c = initialCapacity / ssize;
	        if (c * ssize < initialCapacity)
	            ++c;
	        int cap = MIN_SEGMENT_TABLE_CAPACITY;
	        while (cap < c)
	            cap <<= 1;
	        //创建segments数组并初始化第一个Segment,其余的Segment延迟初始化
	        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); 
	        this.segments = ss;
	    }

put 方法

put 方法步骤:

  1. 定位 segment 并确保 segment 已经初始化。
  2. 调用 Segment 的 put 方法。
		public V put(K key, V value) {
	        Segment<K,V> s;
	        //concurrentHashMap不允许key/value为空
	        if (value == null)
	            throw new NullPointerException();
	        //计算 hash 值
	        int hash = hash(key);
	        //返回的hash值无符号右移segmentShift位与segmentMask进行位运算,定位segment
	        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);
	    }

segmentMask: 段位码,通过 Segments 长度-1得到,因为长度是2的幂次方,所以减一的数的所有bit位都是1,可以更好地保证散列的均匀性。

segmentShift: 段偏移量,segmentShift=32-sshift,计算得出的hash值最大为32位,无符号右移segmentShift,则意味着只保留高几位(其余位是没用的),然后与段掩码segmentMask位运算来定位Segment。


get 方法

get没有使用锁同步,而是使用轻量级同步volatile原语sun.misc.Unsafe.getObjectVolatile(Object, long),保证读到的是最新的对象。

		public V get(Object key) {
		    Segment<K,V> s; 
		    HashEntry<K,V>[] tab;
		    int h = hash(key);
		    long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
		    /*先定位到Segment*/
		    if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
		        (tab = s.table) != null) {
		        /*再遍历segment中的HashEntry数组*/
		        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;
		}

put 方法(Segment中)

由于Segment的put方法需要对共享变量进行写操作,所以put方法是需要加锁操作的,put的步骤:

  1. 判断是否扩容(扩容时不针对整个ConcurrentHashMap,而是只对需要扩容的Segment扩容)
  2. 定位HashEntry
  3. 放置元素
		final V put(K key, int hash, V value, boolean onlyIfAbsent) {
		     /*put方法要加锁*/
		    HashEntry<K,V> node = tryLock() ? null :
		        scanAndLockForPut(key, hash, value);
		    V oldValue;
		    try {
		        HashEntry<K,V>[] tab = table;
		        int index = (tab.length - 1) & hash;
		          /*定位HashEntry,可以看到之前在ConcurrentHashMap的put方法中产生的hash值在定位HashEntry时也用到了*/
		        HashEntry<K,V> first = entryAt(tab, index);
		        for (HashEntry<K,V> e = first;;) {
		            if (e != null) {
		                K k;
		                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 {
		                if (node != null)
		                    node.setNext(first);
		                else
		                    node = new HashEntry<K,V>(hash, key, value, first);
		                int c = count + 1;
		                  /*扩容*/
		                if (c > threshold && tab.length < MAXIMUM_CAPACITY)
		                    rehash(node);
		                else
		                    setEntryAt(tab, index, node);
		                ++modCount;
		                count = c;
		                oldValue = null;
		                break;
		            }
		        }
		    } finally {
		        unlock();
		    }
		    return oldValue;
		}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值