HashMap source code analysis (jdk7)

Original link: https://www.cnblogs.com/fsmly/p/11278561.html

HashMap structure diagram

​ The HashMap of jdk1.7 is implemented by array + singly linked list. Although the hash function is defined to avoid conflicts, because the length of the array is limited, two different keys will have the same position in the array after calculation. It is adopted in version 1.7 To solve the problem with a linked list.

​ From the simple diagram above, it can also be found that if there are too many nodes in the linked list, then it is obvious that the efficiency of searching sequentially through the key value is too low, so it has been improved in 1.8, using array + linked list + red The black tree is implemented. When the length of the linked list exceeds the threshold 8, the linked list is converted to a red-black tree. For details, refer to the in-depth understanding of HashMap in jdk8 summarized in my previous article  .

​ From the above figure, we also know that each element is actually an Entry type, so let's take a look at what attributes are in Entry (In 1.8, Entry was renamed Node, and Map.Entry was also implemented).

//hash标中的结点Node,实现了Map.Entry
static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;
    int hash;
	//Entry构造器,需要key的hash,key,value和next指向的结点
    Entry(int h, K k, V v, Entry<K,V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
    }

    public final K getKey() { return key; }

    public final V getValue() { return value; }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }
    //equals方法
    public final boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry e = (Map.Entry)o;
        Object k1 = getKey();
        Object k2 = e.getKey();
        if (k1 == k2 || (k1 != null && k1.equals(k2))) {
            Object v1 = getValue();
            Object v2 = e.getValue();
            if (v1 == v2 || (v1 != null && v1.equals(v2)))
                return true;
        }
        return false;
    }
	//重写Object的hashCode
    public final int hashCode() {
        return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
    }

    public final String toString() {
        return getKey() + "=" + getValue();
    }

	//调用put(k,v)方法时候,如果key相同即Entry数组中的值会被覆盖,就会调用此方法。
    void recordAccess(HashMap<K,V> m) {
    }

    //只要从表中删除entry,就会调用此方法
    void recordRemoval(HashMap<K,V> m) {
    }
}

back to the top

Member variables in HashMap and their meaning

//默认初始化容量初始化=16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

//最大容量 = 1 << 30
static final int MAXIMUM_CAPACITY = 1 << 30;

//默认加载因子.一般HashMap的扩容的临界点是当前HashMap的大小 > DEFAULT_LOAD_FACTOR * 
//DEFAULT_INITIAL_CAPACITY = 0.75F * 16
static final float DEFAULT_LOAD_FACTOR = 0.75f;

//默认是空的table数组
static final Entry<?,?>[] EMPTY_TABLE = {};

//table[]默认也是上面给的EMPTY_TABLE空数组,所以在使用put的时候必须resize长度为2的幂次方值
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

//map中的实际元素个数 != table.length
transient int size;

//扩容阈值,当size大于等于其值,会执行resize操作
//一般情况下threshold=capacity*loadFactor
int threshold;

//hashTable的加载因子
final float loadFactor;

/**
     * The number of times this HashMap has been structurally modified
     * Structural modifications are those that change the number of mappings in
     * the HashMap or otherwise modify its internal structure (e.g.,
     * rehash).  This field is used to make iterators on Collection-views of
     * the HashMap fail-fast.  (See ConcurrentModificationException).
     */
transient int modCount;

//hashSeed用于计算key的hash值,它与key的hashCode进行按位异或运算
//hashSeed是一个与实例相关的随机值,用于解决hash冲突
//如果为0则禁用备用哈希算法
transient int hashSeed = 0;

back to the top

The construction method of HashMap

​ Let's take a look at the four construction methods provided for us in the HashMap source code.

//(1)无参构造器:
//构造一个空的table,其中初始化容量为DEFAULT_INITIAL_CAPACITY=16。加载因子为DEFAULT_LOAD_FACTOR=0.75F
public HashMap() {
    this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
//(2)指定初始化容量的构造器
//构造一个空的table,其中初始化容量为传入的参数initialCapacity。加载因子为DEFAULT_LOAD_FACTOR=0.75F
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//(3)指定初始化容量和加载因子的构造器
//构造一个空的table,初始化容量为传入参数initialCapacity,加载因子为loadFactor
public HashMap(int initialCapacity, float loadFactor) {
    //对传入初始化参数进行合法性检验,<0就抛出异常
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    //如果initialCapacity大于最大容量,那么容量=MAXIMUM_CAPACITY
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    //对传入加载因子参数进行合法检验,
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        //<0或者不是Float类型的数值,抛出异常
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
	//两个参数检验完了,就给本map实例的属性赋值
    this.loadFactor = loadFactor;
    threshold = initialCapacity;
    //init是一个空的方法,模板方法,如果有子类需要扩展可以自行实现
    init();
}

​ From the above three construction methods, we can find that although the initial capacity is specified, the table at this time is still empty, an empty array, and the expansion threshold threshold is the given capacity or the default capacity (the first two constructions The method is actually done by calling the third one). Before its put operation, an array will be created (similar to the use of parameterless construction in jdk8).

//(4)参数为一个map映射集合
//构造一个新的map映射,使用默认加载因子,容量为参数map大小除以默认负载因子+1与默认容量的最大值
public HashMap(Map<? extends K, ? extends V> m) {
    //容量:map.size()/0.75+1 和 16两者中更大的一个
    this(Math.max(
        	(int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                  DEFAULT_INITIAL_CAPACITY), 
         DEFAULT_LOAD_FACTOR);
    inflateTable(threshold);
    //把传入的map里的所有元素放入当前已构造的HashMap中
    putAllForCreate(m);
}

​ This construction method is to call the inflateTable method before the put operation. The specific function of this method is to create a new table for later use putAllForCreate to load the elements in the incoming map. Let’s take a look at this method and pay attention to it. It is mentioned that the threshold expansion threshold at this time is the initial capacity. Some of these methods are explained below

(1) InflateTable method description

​ This method is more important, it is called in the fourth constructor. And if the first three constructors are used when creating the collection object, this method will be called when the put method is called to initialize the table.

private void inflateTable(int toSize) {
    //返回不小于number的最小的2的幂数,最大为MAXIMUM_CAPACITY,类比jdk8的实现中的tabSizeFor的作用
    int capacity = roundUpToPowerOf2(toSize);
	//扩容阈值为:(容量*加载因子)和(最大容量+1)中较小的一个
    threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    //创建table数组
    table = new Entry[capacity];
    initHashSeedAsNeeded(capacity);
}

(2) RoundUpToPowerOf method description

private static int roundUpToPowerOf2(int number) {
    //number >= 0,不能为负数,
    //(1)number >= 最大容量:就返回最大容量
    //(2)0 =< number <= 1:返回1
    //(3)1 < number < 最大容量:
    return number >= MAXIMUM_CAPACITY
        ? MAXIMUM_CAPACITY
        : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
//该方法和jdk8中的tabSizeFor实现基本差不多
public static int highestOneBit(int i) {
    //因为传入的i>0,所以i的高位还是0,这样使用>>运算符就相当于>>>了,高位0。
    //还是举个例子,假设i=5=0101
    i |= (i >>  1); //(1)i>>1=0010;(2)i= 0101 | 0010 = 0111
    i |= (i >>  2); //(1)i>>2=0011;(2)i= 0111 | 0011 = 0111
    i |= (i >>  4); //(1)i>>4=0000;(2)i= 0111 | 0000 = 0111
    i |= (i >>  8); //(1)i>>8=0000;(2)i= 0111 | 0000 = 0111
    i |= (i >> 16); //(1)i>>16=0000;(2)i= 0111 | 0000 = 0111
    return i - (i >>> 1); //(1)0111>>>1=0011(2)0111-0011=0100=4
    //所以这里返回4。
    //而在上面的roundUpToPowerOf2方法中,最后会将highestOneBit的返回值进行 << 1 操作,即最后的结果为4<<1=8.就是返回大于number的最小2次幂
}

(3) PutAllForCreate method description

​ The method is to traverse the elements in the incoming map collection, and then add them to this map instance. Let's take a look at the implementation details of this method

private void putAllForCreate(Map<? extends K, ? extends V> m) {
    //实际上就是遍历传入的map,将其中的元素添加到本map实例中(putForCreate方法实现)
    for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
        putForCreate(e.getKey(), e.getValue());
}

​ The principle of putForCreate method

private void putForCreate(K key, V value) {
    //判断key是否为null,如果为null那么对应的hash为0,否则调用刚刚上面说到的hash()方法计算hash值
    int hash = null == key ? 0 : hash(key); 
    //根据刚刚计算得到的hash值计算在table数组中的下标
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        //hash相同,key也相同,直接用旧的值替换新的值
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k)))) {
            e.value = value;
            return;
        }
    }
	//这里就是:要插入的元素的key与前面的链表中的key都不相同,所以需要新加一个结点加入链表中
    createEntry(hash, key, value, i);
}

(4)CreateEntry method implementation

void createEntry(int hash, K key, V value, int bucketIndex) {
    //这里说的是,前面的链表中不存在相同的key,所以调用这个方法创建一个新的结点,并且结点所在的桶
    //bucket的下标指定好了
    Entry<K,V> e = table[bucketIndex];
    /*Entry(int h, K k, V v, Entry<K,V> n) {value = v;next = n;key = k;hash = h;}*/
    table[bucketIndex] = new Entry<>(hash, key, value, e);//Entry的构造器,创建一个新的结点作为头节点(头插法)
    size++;//将当前hash表中的数量加1
}

back to the top

HashMap determines the position of the element in the array

​ The algorithm for calculating the hash value in 1.7 is different from the implementation of 1.8, and the hash value is related to the position of our put new element, get finds the element, remove deletes the element to find the subscript through indexFor. So let's take a look at these two methods

(1) Hash method

final int hash(Object k) {
    int h = hashSeed;
    //默认是0,不是0那么需要key是String类型才使用stringHash32这种hash方法
    if (0 != h && k instanceof String) {
        return sun.misc.Hashing.stringHash32((String) k);
    }
    //这段代码是为了对key的hashCode进行扰动计算,防止不同hashCode的高位不同但低位相同导致的hash冲突。简单点
    //说,就是为了把高位的特征和低位的特征组合起来,降低哈希冲突的概率,也就是说,尽量做到任何一位的变化都能对
    //最终得到的结果产生影响
    h ^= k.hashCode();
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

​ We use the following example to illustrate the importance of perturbing the hashCode of the key. We now want to put a Key-Value pair into a map. The value of the Key is "fsmly", and no perturbation is required. The knowledge is pure After simply obtaining the hashcode, the value obtained is " 0000_0000_0011_0110_0100_0100_1001_0010 ". If the length of the table array in the current map is 16, the final index result value is 10. Since the binary of 15 is expanded to 32 bits as "00000000000000000000000000001111", when a number is in bitwise AND operation with him, no matter what the first 28 bits are, the calculation result is the same (because 0 is ANDed with any number, the result is both If the value is 0, then the Entry node of a put is too dependent on the low value of the key's hashCode, and the probability of conflict will also greatly increase). As shown below

​ Because the length of the map array is limited, the method with high probability of conflict is not suitable for use, so hashCode needs to be disturbed to reduce the probability of conflict, and JDK7 uses four bit operations for this processing, or through the following Take a look at this process as a simple example. You can see that the hashCode that has not been disturbed just now has no hash conflicts after the processing.

​ To summarize: we will first calculate the hash value of the key passed in and then use the indexFor method below to determine the position in the table. The specific implementation is to perform a bit operation through a calculated hash value and length-1, then for 2^ For n, after the length minus one is converted into binary, the low-order one is all one (length 16, len-1=15, binary is 1111). The advantage of this setting of the above four disturbances is that each bit of the obtained hashCode will affect the determination of our index position. The purpose is to make the data better hash into different buckets and reduce the hash The occurrence of conflicts. For more principles and details of the hash method in Java collections, please refer to this hash() method analysis

(2) indexFor method

static int indexFor(int h, int length) {
    //还是使用hash & (n - 1)计算得到下标
    return h & (length-1);
}

The main implementation is to perform a bitwise AND operation between the hash value of the calculated key and the length-1 of the array in the map to obtain the array subscript of the put Entry in the table. The specific calculation process is also illustrated in the introduction of the hash method above, so I won't go into details here.

back to the top

Analysis of put method of HashMap

(1) put method

public V put(K key, V value) {
    //我们知道Hash Map有四中构造器,而只有一种(参数为map的)初始化了table数组,其余三个构造器只
    //是赋值了阈值和加载因子,所以使用这三种构造器创建的map对象,在调用put方法的时候table为{},
    //其中没有元素,所以需要对table进行初始化
    if (table == EMPTY_TABLE) {
        //调用inflateTable方法,对table进行初始化,table的长度为:
        //不小于threshold的最小的2的幂数,最大为MAXIMUM_CAPACITY
        inflateTable(threshold);
    }
    //如果key为null,表示插入一个键为null的K-V对,需要调用putForNullKey方法
    if (key == null)
        return putForNullKey(value);
    
    //计算put传入的key的hash值
    int hash = hash(key);
    //根据hash值和table的长度计算所在的下标
    int i = indexFor(hash, table.length);
    //从数组中下标为indexFor(hash, table.length)处开始(1.7中是用链表解决hash冲突的,这里就
    //是遍历链表),实际上就是已经定位到了下标i,这时候就需要处理可能出现hash冲突的问题
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        //hash值相同,key相同,替换该位置的oldValue为value
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            //空方法,让其子类重写
            e.recordAccess(this);
            return oldValue;
        }
    }
	//如果key不相同,即在链表中没有找到相同的key,那么需要将这个结点加入table[i]这个链表中
    
    //修改modCount值(后续总结文章会说到这个问题)
    modCount++;
    //遍历没有找到该key,就调用该方法添加新的结点
    addEntry(hash, key, value, i);
    return null;
}

(2) Analysis of putForNullKey method

​ This method deals with the case where the key is null. When the passed-in key is null, it will start traversing at the table[0] position. The traversal is actually the current linked list with table[0] as the head node. If the key of the node in the linked list is found to be null, then the old value is directly replaced with the value passed in. Otherwise, create a new node and add it to the table[0] position.

//找到table数组中key为null的那个Entry对象,然后将其value进行替换
private V putForNullKey(V value) {
    //从table[0]开始遍历
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
        //key为null
        if (e.key == null) {
            //将value替换为传递进来的value
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue; //返回旧值
        }
    }
    modCount++;
    //若不存在,0位置桶上的链表中添加新结点
    addEntry(0, null, value, 0);
    return null;
}

(3) Analysis of addEntry method

​ The main function of the addEntry method is to determine whether the current size is greater than the threshold, and then determine whether to expand according to the result, and finally create a new node and insert it at the head of the linked list (actually the specified subscript position in the table array)

/*
	hashmap采用头插法插入结点,为什么要头插而不是尾插,因为后插入的数据被使用的频次更高,而单链表无法随机访问只能从头开始遍历查询,所以采用头插.突然又想为什么不采用二维数组的形式利用线性探查法来处理冲突,数组末尾插入也是O(1),可数组其最大缺陷就是在于若不是末尾插入删除效率很低,其次若添加的数据分布均匀那么每个桶上的数组都需要预留内存.
*/
void addEntry(int hash, K key, V value, int bucketIndex) {
    //这里有两个条件
    //①size是否大于阈值
    //②当前传入的下标在table中的位置不为null
    if ((size >= threshold) && (null != table[bucketIndex])) {
        //如果超过阈值需要进行扩容
        resize(2 * table.length);
        //下面是扩容之后的操作
        //计算不为null的key的hash值,为null就是0
        hash = (null != key) ? hash(key) : 0;
        //根据hash计算下标
        bucketIndex = indexFor(hash, table.length);
    }
    //执行到这里表示(可能已经扩容也可能没有扩容),创建一个新的Entry结点
    createEntry(hash, key, value, bucketIndex);
}

(4) Summarize the execution process of the put method

  1. First determine whether the array is empty, if it is an air conditioner, use inflateTable to expand.
  2. Then determine whether the key is null, if it is null, call the putForNullKey method to put. (Here also shows that HashMap allows the key to be null, and the default is inserted at position 0 in the table)
  3. Call the hash() method, perform a hash calculation on the key, and perform & calculate the hash value and the current array length to get the index in the array
  4. Then traverse the linked list under the array index. If the hash of the key is the same as the hash of the passed key and the equals of the key is returned to true, then the value is directly overwritten
  5. Finally, if it does not exist, then insert and create a new node in this linked list

back to the top

Analysis of HashMap's resize method

(1) The general process of resize

void resize(int newCapacity) {
    //获取map中的旧table数组暂存起来
    Entry[] oldTable = table;
    //获取原table数组的长度暂存起来
    int oldCapacity = oldTable.length;
    //如果原table的容量已经超过了最大值,旧直接将阈值设置为最大值
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }
	//以传入的新的容量长度为新的哈希表的长度,创建新的数组
    Entry[] newTable = new Entry[newCapacity];
    //调用transfer
    transfer(newTable, initHashSeedAsNeeded(newCapacity));
    //table指向新的数组
    table = newTable;
    //更新阈值
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

(2) Analysis of transfer method

The transfer method traverses all the entries in the old array, recalculates the index headers one by one according to the new capacity, inserts them and stores them in the new array.

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) {
                //重新计算hash值
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            //这里根据刚刚得到的新hash重新调用indexFor方法计算下标索引
            int i = indexFor(e.hash, newCapacity);
            //假设当前数组中某个位置的链表结构为a->b->c;women 
            //(1)当为原链表中的第一个结点的时候:e.next=null;newTable[i]=e;e=e.next
            //(2)当遍历到原链表中的后续节点的时候:e.next=head;newTable[i]=e(这里将头节点设置为新插入的结点,即头插法);e=e.next
            //(3)这里也是导致扩容后,链表顺序反转的原理(代码就是这样写的,链表反转,当然前提是计算的新下标还是相同的)
            e.next = newTable[i]; 
            newTable[i] = e;
            e = next;
        }
    }
}

​ The main part of this method is that after the hash is recalculated, the difference between the original linked list and the new table's linked list structure can be understood through the following simple diagram, assuming that the position 4 in the original table is a linked list entry1-> entry2->entry3, the subscript calculation of the three nodes in the new array is still 4, then the process is roughly as shown in the figure below

(3) Summary of resize expansion methods

  1. ​ Create a new array (length is twice the original length, if it has exceeded the maximum value, set it to the maximum value)
  2. Call the transfer method to move the entry from the old table to the new array, the details are shown above
  3. Point the table to the new table and update the threshold

back to the top

Analysis of the get method of HashMap

//get方法,其中调用的是getEntry方法没如果不为null就返回对应entry的value
public V get(Object key) {
    if (key == null)
        return getForNullKey();
    Entry<K,V> entry = getEntry(key);
    return null == entry ? null : entry.getValue();
}

​ As you can see, the get method calls getEntry to query the Entry object, and then returns the value of the Entry. So let's take a look at the implementation of the getEntry method

getEntry method

//这是getEntry的实现
final Entry<K,V> getEntry(Object key) {
    //没有元素自然返回null
    if (size == 0) {
        return null;
    }
	//通过传入的key值调用hash方法计算哈希值
    int hash = (key == null) ? 0 : hash(key);
    //计算好索引之后,从对应的链表中遍历查找Entry
    for (Entry<K,V> e = table[indexFor(hash, table.length)];
         e != null;
         e = e.next) {
        Object k;
        //hash相同,key相同就返回
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k))))
            return e;
    }
    return null;
}

getForNullKey method

//这个方法是直接查找key为null的
private V getForNullKey() {
    if (size == 0) {
        return null;
    }
    //直接从table中下标为0的位置处的链表(只有一个key为null的)开始查找
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
        //key为null,直接返回对应的value
        if (e.key == null)
            return e.value;
    }
    return null;
}

back to the top

A simple summary of the implementation of the jdk7 version

​ (1) Because its put operation uses the putForNullKey method to deal with the scenario where the key is null separately, HashMap allows null as the Key

​ (2) When calculating the subscript of the table, call the hash() method according to the hashcode value of the key to obtain the hash value and perform the & operation with the array length-1. The binary bits of length-1 are all 1, which is to be able to Uniform distribution to avoid conflicts (the length is required to be an integer power of 2)
(3) Whether it is get, put or resize, the hashcode of the key will be hashed during the execution process, and the hashcode of the variable object is easy to change. Therefore, HashMap recommends using immutable objects (such as String type) as the Key.
(4) HashMap is not thread-safe, and expansion in a multi-threaded environment may cause an endless loop of the circular linked list, so if you need to operate in a multi-threaded scenario, you can Use ConcurrentHashMap (we will briefly illustrate this situation below)
(5) When a conflict occurs, the HashMap uses the chain address method to handle the conflict.
(6) The initial capacity of the HashMap is set to 16, and if it is simply considered to be 8, the expansion threshold is 6 , The threshold is too small, resulting in frequent expansion; and 32, the space utilization rate may be low.

back to the top

The problem of circular linked list in jdk7 concurrency

​ When talking about the resize method above, we also explained a resize process through an illustrated example, so here we will not demonstrate the execution flow under a single thread. We first remember a few lines of core code in the resize method

Entry<K,V> next = e.next;
//省略重新计算hash和index的两个过程...
e.next = newTable[i]; 
newTable[i] = e;
e = next;

​ The main lines of code of the transfer method called in the resize method are the above four lines. Let's briefly simulate the process of assuming that the two threads thread1 and thread2 perform the resize.

​ (1) Before resizing, suppose the length of the table is 2, suppose now that you add an entry4, you need to expand

​ (2) Assuming that thread1 is now executed to  Entry<K,V> next = e.next; this line of code, then based on the above lines of code, we simply make a comment

​ (3) Then because thread scheduling is the turn of thread2 to execute, suppose thread2 finishes the transfer method (assuming entry3 and entry4 reach the position shown in the figure below after expansion, here we mainly focus on entry1 and entry2 two nodes), then we get The result is

(4) At this time, thread1 is scheduled to continue execution, insert entry1 into the new array, and then e is Entry2. When it is the next cycle, next becomes Entry1 due to the operation of Thread2.

  • First execute newTalbe[i] = e; when thread1 is executed, e points to entry1
  • Then e = next, which causes e to point to entry2 (next points to entry2)
  • And the next cycle of next = e.next, (ie next=entry2.next=entry1, which is the result of thread2 execution) caused next to point to entry1

As shown below

​ (5) Thread1 continues execution, takes entry2 down, puts it in the first position of the bucket of newTable[1], and then moves e and next

(6) e.next = newTable[1] causes entry1.next to point to entry2 . Also note that entry2.next at this time already points to entry1 (the result of thread2 execution is entry2->entry1, see thread2 above is executed Schematic diagram of ), the circular linked list just appeared.

Guess you like

Origin blog.csdn.net/u013804636/article/details/107834974