Why HashMap thread safe?

Introduction: We all know that HashMap is not thread-safe in a multithreaded environment is not recommended, but it is mainly reflected in what thread safe place it, this article will decrypt the problem.

The 1.jdk1.7 HashMap

In jdk1.8 HashMap done a lot of optimization, where the first analysis of the problem in jdk1.7 in, I believe we all know HashMap prone jdk1.7 infinite loop in a multithreaded environment, where we first use the code to simulate appear dead cycle situation:

public class HashMapTest {

    public static void main(String[] args) {
         HashMapThread thread0 = new HashMapThread();
         HashMapThread thread1 = new HashMapThread();
         HashMapThread thread2 = new HashMapThread();
         HashMapThread thread3 = new HashMapThread();
         HashMapThread thread4 = new HashMapThread();
         thread0.start();
         thread1.start();
         thread2.start();
         thread3.start();
         thread4.start();
     }
}

class HashMapThread extends Thread {
    private static AtomicInteger ai = new AtomicInteger();
    private static Map<Integer, Integer> map = new HashMap<>();

    @Override
    public void run() {
        while (ai.get() < 1000000) {
             map.put(ai.get(), ai.get());
             ai.incrementAndGet();
         }
     }
}

The code is relatively simple, is to open a plurality of continuous threads put operation, and HashMap with AtomicInteger are globally shared. In the run several times more than the code appears as follows infinite loop scenario:

There are circumstances in which an array of cross-border several times will appear:

Here we focus on the analysis of why the case of an infinite loop will appear, named view the situation through the cycle of death and jps jstack, results are as follows:

Information can be seen from the stack location infinite loop through which information can be clearly aware of the cycle of death occurred in the HashMap expansion function, the root of the transfer function in, jdk1.7 in the HashMap transfer function is as follows:

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

Summarize the main role of the function:

In the table for expansion of the newTable, the need to transfer data to the original newTable, notice lines 10-12, where it can be seen in the course of the transfer elements, the first interpolation method is used, i.e. reversing the order of the list will be , the key point here is to form an endless loop. Detailed analysis below.

1.1 expansion resulting in an infinite loop analysis

Prerequisites:

It is assumed that

# 1.hash simple algorithm with key mod list size.

# 2. Initially the hash table size = 2, key = 3,7,5, are in the table [1] in.

# 3. Then resize, so that size into 4.

No data structure prior to resize follows:

If in single-threaded environment, the final results are as follows:

Transfer process here, not described in detail, what to do once you understand transfer function in which the transfer process and how to invert list should not be difficult.

Then in a multithreaded environment, assume that there are two threads A and B during the put operation. A thread in the implementation of the transfer function of the first 11 lines of code to hang because the function in the analysis position is very important here, so posted again.

A thread in this case results are as follows:

After the suspended thread A, then thread B executed normally, and resize operation is completed, the results are as follows:

It should pay particular attention to the point: Because thread B have been implemented, according to the Java memory model, and now newTable Entry table in the main memory are the latest value: 7.next = 3,3.next = null.

At this time, switching to the thread A, the value in memory A suspended thread as follows: e = 3, next = 7, newTable [3] = null, code execution process is as follows:

newTable[3]=e ----> newTable[3]=3
e=next ----> e=7

In this case the following results:

Continue cycle:

e=7
next=e.next ----> next=3【从主存中取值】
e.next=newTable[3] ----> e.next=3【从主存中取值】
newTable[3]=e ----> newTable[3]=7
e=next ----> e=3

The results are as follows:

Circulate again:

e=3
next=e.next ----> next=null
e.next=newTable[3] ----> e.next=7 即:3.next=7
newTable[3]=e ----> newTable[3]=3
e=next ----> e=null

Note that the cycle: e.next = 7, and in the last cycle 7.next = 3, occur circular linked list, and at this time e = null cycle ends.

The results are as follows:

As far as the data structure of the polling hashmap, an endless loop will occur here in subsequent operations, in tragedy.

1.2 expansion resulting in loss of data analysis process

Follow the above analysis, initially:

Thread A and thread B is put operation, the same thread A pending:

A thread running at the time the results are as follows:

At this time, thread B has received no CPU time slices, and complete the resize operation:

Also note that due to the implementation of the completion thread B, newTable and table are the latest value: 5.next = null .

At this time, switching to the thread A, the thread hangs A: E =. 7, =. 5 Next, NewTable [. 3] = null.

Performing newtable [i] = e, ** 7 will be placed on table [3] ** the position where the next = 5. Then the next cycle:

e=5
next=e.next ----> next=null,从主存中取值
e.next=newTable[1] ----> e.next=5,从主存中取值
newTable[1]=e ----> newTable[1]=5
e=next ----> e=null

5 will be placed in the table [1] position, where e = null cycle is ended, 3 loss element , and is formed circular linked list . And cause an infinite loop during subsequent operations hashmap.

2.jdk1.8中HashMap

In jdk1.8 to HashMap optimized, collision occurs hash, no longer uses the first interpolation mode, but directly into the tail of the list, so the situation does not appear circular linked list, but still unsafe in the case of multi-threaded here we see the HashMap put in operation jdk1.8 source:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null) // 如果没有hash碰撞则直接插入元素
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; 
        K k;
    if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
        e = p;
    else if (p instanceof TreeNode)
        e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
    else {
        for (int binCount = 0; ; ++binCount) {
            if ((e = p.next) == null) {
                p.next = newNode(hash, key, value, null);
                if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                    treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
        if (!onlyIfAbsent || oldValue == null)
            e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

This is the main function of the HashMap jdk1.8 put in operation, note that the code line 6, if there is no hash collision occurs directly into the element. If thread A and thread B is put operation while just the two different data as a hash value, and the position data is null, so that the thread A, B will enter the code line 6. Suppose a situation, the thread A is inserted into the pending data has not been performed, and the thread B executed normally, the normal data to be inserted, and CPU time slice acquisition thread A, then thread A hash no further determination, the problem : thread a thread B will insert data to the cover , the occurrence of thread-safe.

Here is a brief analysis reflects lower jdk1.8 appear in HashMap thread insecurity, will follow-up on java collections framework is summarized, and then to the specific analysis.

to sum up

First HashMap is not thread-safe , which mainly reflected:

# 1. In jdk1.7, in a multithreaded environment, it will result in an endless chain or data loss when expansion.

# 2. In jdk1.8, in a multi-threaded environment, data coverage of the case will take place.

Published 50 original articles · won praise 1706 · Views 2.22 million +

Guess you like

Origin blog.csdn.net/zl1zl2zl3/article/details/105233273