ConcurrentHashMap concurrent containers

  JDK5 added new concurrent package, container relative synchronization, and transmits the container through a number of mechanisms to improve concurrency. Because synchronous container will all have access to the container status

Serialized, thus ensuring the safety of the thread, so the cost of this approach is severely reduces concurrency when multiple threads compete container throughput severely reduced. Therefore Java5.0 open

Beginning for multi-threaded concurrent access design provides a better concurrent performance of concurrent containers, it introduced the java.util.concurrent package. With Vector and Hashtable,

Collections.synchronizedXxx () synchronization containers compared to concurrent containers util.concurrent introduced mainly to solve two problems: 
1) is designed according to a specific scenario, to avoid the synchronized, provides concurrency. 
2) defines the number of concurrent security complex operation, and to ensure that the iteration operation in a concurrent environment can not go wrong.

  util.concurrent container while iterating, can not be encapsulated in a synchronized, you can guarantee not to throw an exception, but not necessarily every time I see are "up to date, the current" data.

  The following is a brief introduction to concurrent containers:

  Instead of synchronization ConcurrentHashMap Map (Collections.synchronized (new HashMap ())), is well known, the HashMap is a hash value segment stored synchronization when synchronization lock Map all segments, and when the lock according ConcurrentHashMap hash value hash values ​​locked lock corresponding period, thereby increasing concurrency. ConcurrentHashMap also adds support for common complex operations, such as "If you do not add": putIfAbsent (), replace: replace (). These two operations are atomic operations.

  And were replaced CopyOnWriteArrayList CopyOnWriteArraySet Set List and, in place of the main synchronization Set List, and in the case of synchronous operation of the main traversal down, which is the idea described above: to ensure that no mistakes iterative process, in addition to the lock, a further method is the "clone" container object.

  ConcurrentLinkedQuerue is a FIFO queue. It is non-blocking queue.

    ConcurrentSkipListMap may alternatively SoredMap (e.g. Collections.synchronzedMap packed with the TreeMap) in the high concurrency.

  ConcurrentSkipListSet may alternatively SoredSet (e.g. Collections.synchronzedSet packed with the TreeMap) in the high concurrency.

ConcurrentHashMap internal structure

  ConcurrentHashMap order to improve their own concurrency in the internal use of a structure called Segment of a Segment structure Hash Table is actually a class, internal Segment maintains a list array

A positioning element ConcurrentHashMap process requires two Hash operation, the first target Segment Hash, Hash second element is positioned to the head of the list is located, and therefore, a side effect of this is a construction process Hash HashMap longer than normal, but the benefits it is time to write the elements can only be locked to where Segment, the Segment will not affect the other, so that, in the best case, ConcurrentHashMap can while supporting the highest number of write operations Segment size (just write these operations are very evenly distributed across all of the Segment), so that by this kind of structure, ConcurrentHashMap concurrent capacity can be greatly improved.

Segment

  Let's look at a specific data structure Segment of:

static final class Segment<K,V> extends ReentrantLock implements Serializable {
    transient volatile int count;
    transient int modCount;
    transient int threshold;
    transient volatile HashEntry<K,V>[] table;
    final float loadFactor;
}

  Explain in detail the meaning of Segment member variables inside:

  • count: the number of elements in Segment
  • modCount: number of operations resulting impact on the size of the table (such as put or remove operation)
  • threshold: the threshold number, Segment elements which exceed this value will still be the expansion of the Segment
  • table: list array, each array element represents the head of a linked list
  • loadFactor: load factor, for determining the threshold

hash Entry

  Segment the elements are stored in a linked list HashEntry form the array, look at the structure of HashEntry:

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

  We can see a feature HashEntry, in addition to value, several other variables are final and do so in order to prevent the chain structure is destroyed, there ConcurrentModification situation.

ConcurrentHashMap initialization

  Let's combine the source code to achieve specific analysis of ConcurrentHashMap, look under the initialization method:

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;
    }
    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);
}

   CurrentHashMap initializing a total of three parameters, a initialCapacity, represents an initial capacity, a loadFactor, represents the load parameter, the last one is concurrentLevel, the number of Segment inside ConcurrentHashMap representatives, ConcurrentLevel a designated, can not be changed, the subsequent number if ConcurrentHashMap element ConrruentHashMap lead to increased capacity is needed, ConcurrentHashMap without increasing the number Segment, which will only increase the capacity of the size of the Segment in the list of the array, so that the benefits do not need to rehash the expansion process for the entire ConcurrentHashMap, but only need to do once inside the elements of Segment rehash it.

  ConcurrentHashMap entire initialization method is still very simple, first concurrentLevel according to a new Segment, where the number is not more than concurrentLevel Segment of the largest power of two, that is the number Segment is always an index of 2, this benefit is convenience using shift operations to hash, to speed up the process of hash. The next step is to determine the size intialCapacity Segment capacity according to the capacity of the size of each Segment index is 2, so the same hash in order to accelerate the process.

  Here require special attention to two variables, namely segmentShift and segmentMask, these two variables will play a significant role in the back, assuming that the constructor is to determine the number of Segment 2 ^ n, then segmentShift is equivalent to 32 subtracts n, and n is equal to 2 ^ segmentMask minus one.

ConcurrentHashMap the get operation

  ConcurrentHashMap mentioned earlier the operation is not to get locked, here we look at its implementation:

public V get(Object key) {
    int hash = hash(key.hashCode());
    return segmentFor(hash).get(key, hash);
}

Look at the third line, segmentFor This function is used to determine which operations should be carried out in a segment, almost all operations on ConcurrentHashMap the need to use this function, we look to achieve this function:

final Segment<K,V> segmentFor(int hash) {
    return segments[(hash >>> segmentShift) & segmentMask];
}

   This function is used to determine the bit manipulation Segment, according to the incoming hash value unsigned right segmentShift bit to the right, and then segmentMask with the operation, said the combined value before we segmentShift and segmentMask, we can draw the following conclusions: assuming that the number is the n-th power of Segment 2, according to the high-bit hash value of n elements can determine exactly where a Segment of the element.

  In determining the Segment's get method requires a segment in which to operate in the future, the next thing is to call the corresponding:

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;
}

   Look at the second line, where a judge had to count, which count represents the number of elements Segment, we can look at the definition of count:

transient volatile int count;

   You can see the count is volatile, in fact here which utilizes the volatile semantics:

  In the same field in each subsequent read operation to a write operation happens-before volatile field.

  Because in fact put, remove and other operations will be updated count value, so when competition occurs, volatile semantics can guarantee a write operation before a read operation, write operation will ensure that the subsequent read operations are visible, so follow-up operations can get back get the complete content of the element.

  Then, in the third line, called getFirst () to get the head of the list:

HashEntry<K,V> getFirst(int hash) {
    HashEntry<K,V>[] tab = table;
    return tab[hash & (tab.length - 1)];
}

   Similarly, there is also used to determine the position of the head of the list operation, the length of the hash value and a do HashTable reduction operation, lower n bits of the final result is the hash value, where n is the length of the base 2 HashTable results.

  In the head of the list is determined after the entire list can be traversed, see line 4, extracted key value corresponding to the value, if the value is out of the value of null, then the key may be, it is put on the value of process, if this is the case, then remove the lock to ensure that the value is complete, if it is not null, then direct return value.

ConcurrentHashMap the put operation

  Reading the get operation, look at the put operation, the operation is put in front of Segment determination process, not repeated here, looking directly put method key segment of:

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();
    }
}

   First, put the operation of Segment is locked completed, then in the fifth row, if the number of Segment elements exceeds the threshold value (calculated by the constructor loadFactor) which is required for Segment expansion, and to be rehash, about rehash of we can own process to understand, here it is not talked about in detail.

  Action lines 8 and 9 is getFirst process of determining the position of the head of the list.

  Line 11 of this while loop here is to find and to put elements with the same key elements in the list, if found, will directly update update key of value, if not found, then enter 21 lines here to generate a new HashEntry and Segment add it to the head of the whole, and then update the value of count.

ConcurrentHashMap remove operation

  And the front portion of the front of the get and put operations Remove operation, are positioned Segment procedure, and then calls the remove method of Segment:

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();
    }
}

   First remove operation is also to determine the location you want to delete the elements, but here remove elements of the method is not simply a next element to be deleted in front of the elements pointing to the back of a get away, before we have said in the next HashEntry is final the future will not be modified once the assignment, after positioning to position elements to be deleted, the program will be in front of removing elements that replicate some elements all over again, and then again to the list one by one up

ConcurrentHashMap size of the operation

  In the previous section, we involved in the operation are carried out in a single Segment in ConcurrentHashMap but some operations are performed in a plurality of Segment, such as the operation size, the size ConcurrentHashMap operation also uses a more clever way to avoid are locked for all Segment.

  We mentioned a Segment of a modCount variable represents the number of operations Segment impact on the number of elements, this value only to rise, size operation is traversed twice Segment, each Segment record modCount value, and then compares modCount twice, if the same, then the write operation had not occurred during the traversal of the original will return a result, if not the same, the process was repeated put again, if they are not identical, we will need all of Segment are locked, then a traverse of a specific implementation ConcurrentHashMap we can see the source code, here is not posted.

Guess you like

Origin www.cnblogs.com/deityjian/p/11373389.html