Java基础-ConcurrentHashMap源码分析

1.官方解释:

 

1)支持获取的完全并发和更新的所期望可调整并发的哈希表。此类遵守与 Hashtable 相同的功能规范,并且包括对应于 Hashtable 的每个方法的方法版本。不过,尽管所有操作都是线程安全的,但获取操作不 必锁定,并且不 支持以某种防止所有访问的方式锁定整个表。此类可以通过程序完全与 Hashtable 进行互操作,这取决于其线程安全,而与其同步细节无关。 

2)获取操作(包括 get)通常不会受阻塞,因此,可能与更新操作交迭(包括 put 和 remove)。获取会影响最近完成的 更新操作的结果。对于一些聚合操作,比如 putAll 和 clear,并发获取可能只影响某些条目的插入和移除。类似地,在创建迭代器/枚举时或自此之后,Iterators 和 Enumerations 返回在某一时间点上影响哈希表状态的元素。它们不会 抛出 ConcurrentModificationException。不过,迭代器被设计成每次仅由一个线程使用。 

3)这允许通过可选的 concurrencyLevel 构造方法参数(默认值为 16)来引导更新操作之间的并发,该参数用作内部调整大小的一个提示。表是在内部进行分区的,试图允许指示无争用并发更新的数量。因为哈希表中的位置基本上是随意的,所以实际的并发将各不相同。理想情况下,应该选择一个尽可能多地容纳并发修改该表的线程的值。使用一个比所需要的值高很多的值可能会浪费空间和时间,而使用一个显然低很多的值可能导致线程争用。对数量级估计过高或估计过低通常都会带来非常显著的影响。4)当仅有一个线程将执行修改操作,而其他所有线程都只是执行读取操作时,才认为某个值是合适的。此外,重新调整此类或其他任何种类哈希表的大小都是一个相对较慢的操作,因此,在可能的时候,提供构造方法中期望表大小的估计值是一个好主意。 

5)此类及其视图和迭代器实现了 Map 和 Iterator 接口的所有可选 方法。 

6)此类与 Hashtable 相似,但与 HashMap 不同,它不 允许将 null 用作键或值。 

 

2.数据结构图

简单解释一下就是他是有一个固定长度的Segment(段)数组组成,Segment数组不支持扩容,默认初始化16的长度,所以理论上可以同时支持16的最大并发,Segment是由一个数组加链表组成的数据结构,Segment是支持动态扩容的。

 

3.包结构类图

 

4.属性解释:

    final int segmentMask;//分段索引的掩码值。键的哈希码的上位被用于选择该段。
    final int segmentShift;//在段中为索引的移位值。
    final Segment<K,V>[] segments;//分段,每一个都是一个专门的哈希表

5.方法详解:

构造函数:public ConcurrentHashMap() 

   public ConcurrentHashMap() {
        this(16, 0.75f, 16);
    }
    public ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
        if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();

        if (concurrencyLevel > MAX_SEGMENTS)
            concurrencyLevel = MAX_SEGMENTS;

        // Find power-of-two sizes best matching arguments
        int sshift = 0;
        int ssize = 1;
        while (ssize < concurrencyLevel) {
            ++sshift;
            ssize <<= 1;
        }
		//用默认值,concurrencyLevel 为 16,sshift 为 4
        // 那么计算出 segmentShift 为 28,segmentMask 为 15,后面会用到这两个值
        segmentShift = 32 - sshift;//
        segmentMask = ssize - 1;//
        this.segments = Segment.newArray(ssize);

        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        int c = initialCapacity / ssize;
        if (c * ssize < initialCapacity)
            ++c;
        int cap = 1;
        while (cap < c)
            cap <<= 1;
        //初始化
        for (int i = 0; i < this.segments.length; ++i)
            this.segments[i] = new Segment<K,V>(cap, loadFactor);
    }

这是jdk1.6初始化方法,jdk1.7有做优化,就不写在这里了,

public V put(K key, V value)

    public V put(K key, V value) {
        if (value == null)
            throw new NullPointerException();
        int hash = hash(key.hashCode());
        return segmentFor(hash).put(key, hash, value, false);
    }	
    final Segment<K,V> segmentFor(int hash) {
        return segments[(hash >>> segmentShift) & segmentMask];
    }	
	
    V put(K key, int hash, V value, boolean onlyIfAbsent) {
	lock();
	try {
		int c = count;
		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;
		if (e != null) {
			oldValue = e.value;
			if (!onlyIfAbsent)
				e.value = value;
		}
		else {
			oldValue = null;
			++modCount;
			tab[index] = new HashEntry<K,V>(key, hash, first, value);
			count = c; // write-volatile
		}
		return oldValue;
	} finally {
		unlock();
	}
}

思路:key找到hash,hash找到对应的槽Segment,然后调用segment的put方法进行操作,在使用put方法的时候首先进行锁定,然后检查Segment的数组容量是否够,不够就扩容,然后通过hash找到该key对应的hashEntry的下标,找到位子就进行put操作。

 

 public V get(Object key)

    public V get(Object key) {
        int hash = hash(key.hashCode());
        return segmentFor(hash).get(key, hash);
    }	
    V get(Object key, int hash) {
		if (count != 0) { // read-volatile
			//这里通过hash值找到对应的位子
			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;
					return readValueUnderLock(e); // recheck
				}
				e = e.next;
			}
		}
		return null;
	}	
	

这个方法比较简单,通过key找到segment,然后通过hash找到HashEntry,然后取出链表的第一个HashEntry进行对比。

Set<Map.Entry<K,V>> entrySet()这个方法和hashMap的类似,只是他的remove方法是调用的ConcurrentHashMap的remove方法,下面看看remove

public V remove(Object key)

public V remove(Object key) {
	int hash = hash(key.hashCode());
        return segmentFor(hash).remove(key, hash, null);
    }
   V remove(Object key, int hash, Object value) {
		lock();
		try {
			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;
					// All entries following removed node can stay
					// in list, but all preceding ones need to be
					// cloned.
					++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 = c; // write-volatile
				}
			}
			return oldValue;
		} finally {
			unlock();
		}
	}	

操作和put有点类似,也是需要上锁,所以put和remove方法都是并发安全的。

 

 

 

展开阅读全文

没有更多推荐了,返回首页