Implementation of the Java concurrent programming --- ConcurrentHashMap of (a)

Foreword

In the container application daily key-value pairs, we are most accustomed to using HashMap, but people familiar system of this container should all know it is not thread-safe, then we have to use another Map implementations in concurrent programming - ConcurrentHashMap.

1. Introduction ConcurrentHashMap between us first understand why a multi-threaded environment can not use HashMap?
HashMap in a multithreaded PUT () operation will cause an infinite loop, it will make HashMap in the Entry list data structure will produce circular ( That end to end), when we use the next () will never find the end.

2. Learn ConcurrentHashMap, we should have some basic knowledge of reserves
1) Hash: Hash , Hash: the input of any length by an algorithm (hash), converted into a fixed length output, the output value is our hash value, belongs to the compression map, but this method is also prone to hash collision. Hash algorithm in the application of the most commonly used method is to directly take over law.
Hash solution to the conflict: OpenAddressed re-hash chain address law

In real life application in md4, md5, sha belong hash algorithm, also known as the digest algorithm (the algorithm is not reversible)

2) calculating bits : I bits, or bits, bit non-bit XOR signed left, right signed, unsigned shift right, taking touch operation (often seen in the source code)
Details can refer: Bit understand the operation of the relevant

3 Jdk1.7 and before and after Jdk1.8 a big difference to the achievement of ConcurrentHashMap, today let's take a look at version 1.7 and earlier implemented.
(1) to look at its internal structure
Here Insert Picture Description
(2) look at the method of its construction:
Here Insert Picture Description
It has three parameters initialized:
1) initalCapacity : the size of the initial capacity , the default is 16
2) loadFactor : expansion factor , default 0.75, when the number of memory elements is greater than a Segment initalCapacity * loadFactor when the Segment will once expansion.
. 3) concurrentLevel : concurrency, default is 16, concurrency can simultaneously be appreciated that the program update time does not run ConcurrentHashMap maximum number of threads of lock contention actually ConcurrentHashMap lock the segment number , i.e. Segment [] of array length, if the degree of concurrency is set too small, will cause serious lock contention problems if concurrency is set too high, originally located in a visit to a different Segment Segment diffuse in, cache hit rate of the CPU will drop, causing performance degradation program.
(Note that when performing the expansion, Segment is not expansion, but only expansion following table array, the array is doubled for expansion.)

Constructor portion doubts Code: (in Jdk1.7)
A.

while (ssize < concurrentLevel){
	++ sshift;
	ssize <<=1;
}

Explanation: Segment guarantee size of the array, is necessarily a power of 2, for example, a user is provided a degree of concurrency 17, the actual array size is 32 Segmnet.

b.

int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c)    cap << =1;

解释说明:保证每个Segment中table数组的大小,一定为2的幂,初始化的三个参数取默认值的时候,table数组的大小为2。

3)两个常用方法get()和put()方法的实现:

 v  get(Object key, int hash); 获取相应元素  
注意:此方法并不加锁,因为只是读操作,
 V put(K key, int hash, V value, boolean onlyIfAbsent)
注意:此方法加锁

get() :
1)定位Segment:先对key取hashcode,再对该值进行再散列值的高位,然后和该Segment的长度取模。
2)定位table:先对key去hashcode,再对该值进行再散列,然后和table的长度取模。
3)依次扫描链表,要么找到元素,要么返回null

源码:

// 外部类方法
public V get(Object key) {
    int hash = hash(key.hashCode());
    return segmentFor(hash).get(key, hash); // 第一次hash 确定段的位置
}

//以下方法是在Segment对象中的方法;

//确定段之后在段中再次hash,找出所属链表的头结点。
final Segment<K,V> segmentFor(int hash) {    
    return segments[(hash >>> segmentShift) & segmentMask];
}

V get(Object key, int hash) {
    if (count != 0) { // read-volatile
        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;
}

put():1)首先定位Segment,当这个Segment在map初始化后,还为null,它会由ensureSegment()方法负责填充这个Segment.
2)对Segment进行加锁
3)定位所在的table元素,并扫描table下的链表,找到这个元素时,put() 操作会覆盖原来的值,putIfAbsent()方法不会覆盖原来的值,然后中断循环,返回原来的值给调用者。如果没有找到的时候,将其放置链表尾部。

源码:

//外部类方法
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);  //先确定段的位置
    }

   // Segment类中的方法    
V put(K key, int hash, V value, boolean onlyIfAbsent) {
    lock();
    try {
        int c = count;
        if (c++ > threshold) // 如果当个数超过阈值,就重新hash当前段的元素 ,
            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();
    }
}

(4) size () method:
when to size () method performs two unlocked statistics , direct return consistent results. Inconsistent, re-lock again statistics (which can improve operating efficiency to a large extent, but to get the size of the container is only a rough value )
(Note: This also reflects the weak consistency of the container; the container get () and containsKey () methods are not locked, so there may be value in the process, the other thread it has been operated, causing the result of inconsistency .)

Many people have doubts that such a vessel would not be very safe, very unreasonable it?
In actual fact, JDK developers elemental clever design ensures acquired by volatile keyword is up to date.
On implementation, the data HashEntry store key-value pairs, redesign its member variables of value types are all volatile, thus ensuring another thread changes to the value value, another action of the thread can be seen immediately , so that a good solution to the problem of weak consistency of the get () methods of the.

Source:

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

to sum up

In JDK1.7 for the realization ConcurrentHashMap, there are many details still difficult to get to know, but as long as we understand it's constructor, put (), get () and size () is simple to use and its principles, will be able to coping well with the interview and our daily use. Do not pull the source code line by line, so we only got into it, and feel headaches.

Published 19 original articles · won praise 2 · Views 413

Guess you like

Origin blog.csdn.net/TheWindOfSon/article/details/103979122