Map 源码分析

与List、Set、Queue不同,Map是以<K,V>结构进行存储,其中Map接口是一个顶级接收,它定义了操作一些Map的基本方法,下面的继承图展示了一些常用的Map继承结构

从源码的角度,来理解这些Map,分析它们之间具体的实现区别

一、HashMap

1.1 基本原理

HashMap的存储在JDK1.7和JDK1.8有一些明显的区别,在JDK1.7中,HashMap用数组+链表的形式进行存储,而在JDK1.8及以上的版本中,HashMap采用数组+链表+红黑树的形式进行存储。

HashMap添加、获取、移除元素的操作,都基于HashMap实现的hash算法,该算法根据KEY计算出一个int类型的hash值,然后根据hash值与数组的最大下标进行按位与运算(并不是用%取模),最后就会得到元素所在数组中的位置,如果当前数组位置没有元素,则直接把元素方法数组中即可;如果当前位置已经有元素,则会在当前位置形成一个链表结构。

而在JDK1.7和JDK1.8以上版本,当hash冲突时,形成链表的方式也有所不同。

JDK1.7主要采用头插法,即新插入的元素放入到数组位置,原有的元素则连接在该元素的后面;

JDK1.8主要采用尾插法,即新加入的元素,直接连接在链表的尾部

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

通过上面的分析,可以看出,HashMap的元素组成应该包含了hash值、KEY、VALUE以及下一个节点的对象

HashMap中通过静态内部类Node来定义了一个元素节点的结构,该类实现了Map接口里面定义的一个Entry接口

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;

    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }
}

HashMap使用table来记录数组上的元素,使用entrySet来记录所有元素

transient Node<K,V>[] table;

transient Set<Map.Entry<K,V>> entrySet;

1.2 构造方法

JDK1.8中HashMap提供了三个构造方法,这三个构造方法中,都会给loadFactor赋值,loadFactor是一个HashMap的一个重要属性,主要用于数组的扩容,如果没有指定loadFactor,就会使用默认的0.75

static final float DEFAULT_LOAD_FACTOR = 0.75f;

public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}

public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

在JDK1.8中,如果构造方法中指定了数组的容量,则会去调用tableSizeFor()方法,该方法会对传入的容量进行移位和与运算,最后的得到一个2n大小的数字,这个2n可以等于指定的容量,或者等于一个比cap大但最接近cap的值,比如指定初始容量为25,通过一系列运算得到的容量大小为32。如果指定的容量小于等于16,那么初始容量最小就是16而不是8,即便指定了初始容量为4,经过运算得到的初始容量也是16。

HashMap定了默认的容量DEFAULT_INITIAL_CAPACITY就是16

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

1.3 PUT方法

1.3.1 JDK1.7的实现

JDK1.7中通过Entry类来存储元素信息,与JDK1.8中的Node是一样的,都是实现了Map.Entry接口

通过createEntry()方法可以看出,先把原来数组中的元素拿出来,然后创建一个新的元素,而老的元素作为新元素的链表上的下一个节点,然后把新的Entiry放置在数组上,这就是典型的头插法

public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key);
    int i = indexFor(hash, table.length);

    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

void addEntry(int hash, K key, V value, int bucketIndex) {
    if ((size >= threshold) && (null != table[bucketIndex])) {
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }

    createEntry(hash, key, value, bucketIndex);
}

void createEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    size++;
}

注:这种头插法的方式,在多线程的情况下,如果两个线程都在进行扩容,都进入到了transfer()方法中时,可能会出现死循环

1.3.2 JDK1.8实现

JDK1.8的putVal()方法要比JDK1.7的put()方法复杂很多,如果没有HashMap还没有初始化,则调用resize()来进行初始化,resize()方法最重要的作用的是用来扩容,HashMap的threshold属性记录了扩容的阈值,它是通过容量和加载因子的乘积得到的。

如果数组中当前位置的没有元素,则直接调用newNode创建一个新的元素,填充数组中对应的位置

如果当前元素的KEY已经存在数组中,则把当前元素的值覆盖掉,然后返回原来的值

如果当前数组位置上的节点是一个TreeNode节点(红黑树节点),则调用putTreeVal()方法,将生成的新的元素节点添加到这个数组节点的红黑树上

如果这是一个普通的Node节点,就遍历数组节点下的链表,查找与当前KEY一样的节点,如果没有找到,就生成一个新的元素节点,添加在链表的最后面,然后会去判断,当前链表上的节点数是否大于等于8个,HashMap中定义了TREEIFY_THRESHOLD = 8,当到达这个阈值时,需要就调用treeifyBin()方法,将这个链表转换成一颗红黑树,关于红黑树的结构,后面写一篇专门的文章进行介绍

最后判断判断HashMap中元素的个数,是否达到了数组扩容的阈值

static final int TREEIFY_THRESHOLD = 8;

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

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

put()方法中,如果数组元素下链表的长度大于等于8,就会把链表转换成为红黑树,那么什么时候把红黑树转换成链表呢?并不是在remove元素的时候,而是在数组扩容的时候

在resize()方法中,如果数组节点是一个TreeNode,会去调用split()进行rehash,这是时候如果红黑树的节点数小于等于6,就会把红黑树在转换成链表结构

static final int UNTREEIFY_THRESHOLD = 6;

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
	……
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
				……
            }
        }
    }
    return newTab;
}

final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
    ……
    if (loHead != null) {
        if (lc <= UNTREEIFY_THRESHOLD)
            tab[index] = loHead.untreeify(map);
        else {
            tab[index] = loHead;
            if (hiHead != null) // (else is already treeified)
                loHead.treeify(tab);
        }
    }
    if (hiHead != null) {
        if (hc <= UNTREEIFY_THRESHOLD)
            tab[index + bit] = hiHead.untreeify(map);
        else {
            tab[index + bit] = hiHead;
            if (loHead != null)
                hiHead.treeify(tab);
        }
    }
    ……
}

二、LinkedHashMap

2.1 存储结构

LinkedHashMap继承了HashMap,它继承了HashMap的所有方法,并没有进行重写,唯一不同的点在于它的存储结构发生了变化

LinkedHashMap的内部类Entry继承了HashMap的Node类,同时又新增了两个before和after属性,这两个属性分别指向前后两个Entry节点,这与Node节点里面的next属性不同,next属性维持的是链表和红黑树的结构,而before和after存储的是节点插入的顺序

static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

LinkedHashMap中使用head和tail来记录最早插入和最后插入的元素节点

/**
 * The head (eldest) of the doubly linked list.
 */
transient LinkedHashMap.Entry<K,V> head;

/**
 * The tail (youngest) of the doubly linked list.
 */
transient LinkedHashMap.Entry<K,V> tail;

2.2 保序实现

在HashMap的putVal()方法中,最后会去调用afterNodeInsertion()方法,HashMap中并没有实现该方法,而在LinkedHashMap中,对方法进行了实现

同类型的还有afterNodeAccess()和afterNodeRemoval()方法

afterNodeAccess()方法会在元素节点的值发生变化时调用,比如replace()方法,或者put()方法的KEY相同时,都会调用该方法

而afterNodeRemoval()方法会在removeNode()方法中进行调用

void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    ……
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

LinkedHashMap中重写了newNode()方法,当调用put()方式时,会调用重写的newNode()方法,在该方法中,会生成一个LinkedHashMap.Entry的对象,然后调用linkNodeLast()方法把新插入的Entry节点,放置在链表的最后面,这样在遍历的时候,就可以通过插入的顺序来遍历了。

Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    LinkedHashMap.Entry<K,V> p =
        new LinkedHashMap.Entry<K,V>(hash, key, value, e);
    linkNodeLast(p);
    return p;
}

private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    LinkedHashMap.Entry<K,V> last = tail;
    tail = p;
    if (last == null)
        head = p;
    else {
        p.before = last;
        last.after = p;
    }
}

LinkedHashMap的afterNodeAccess()方法,在replace()和put()方法KEY相同时都会调用该方法,我们看下该方法具体实现了什么

在该方法中,如果accessOrder属性为true,会把操作的元素从顺序链表的现有位置移动到最新的位置。

void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    if (accessOrder && (last = tail) != e) {
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        p.after = null;
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a != null)
            a.before = b;
        else
            last = b;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
}

而accessOrder属性,只有在下面的构造方法可以指定为true,而其他的构造方法,默认都是false

public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

三、TreeMap

TreeMap实现了SortedMap接口,可以进行排序,而排序自然需要一个比较强,在创建TreeMap实例的时候,可以自定义比较器的实现

public TreeMap(Comparator<? super K> comparator) {
    this.comparator = comparator;
}

3.1 存储结构

TreeMap的存储结构,采用红黑树来实现,其内部类Entry定义了元素的KEY和VALUE,以及它的父节点、左节点、右节点以及节点颜色

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable{

    private final Comparator<? super K> comparator;

    private transient Entry<K,V> root;
}

static final class Entry<K,V> implements Map.Entry<K,V> {
    K key;
    V value;
    Entry<K,V> left;
    Entry<K,V> right;
    Entry<K,V> parent;
    boolean color = BLACK;

    Entry(K key, V value, Entry<K,V> parent) {
        this.key = key;
        this.value = value;
        this.parent = parent;
    }
}

3.2 PUT方法

在put()方法中,会根据是否指定了comparator比较器分情况进行比较

如果指定了比较器,就去调用比较器的compare()方法进行比较,如果当前KEY比节点的KEY小,去获取左节点继续比较,如果找到了相同KEY的,就直接覆盖原有的值返回

如果没有指定比较器,就会把KEY转换成Comparable类型,所以,在创建TreeMap实例的时候,如果没有指定比较器,就需要KEY必须实现Comparable接口的compare()方法,不然就会报ClassCastException异常

如果没有找到与当前KEY匹配的,就新创建一个Entry实例,然后根据前面与最后一个节点比较的结果,将其添加到左或右节点上,然后调用fixAfterInsertion()方法,来对红黑树进行平衡

public V put(K key, V value) {
    Entry<K,V> t = root;
    if (t == null) {
        compare(key, key); // type (and possibly null) check

        root = new Entry<>(key, value, null);
        size = 1;
        modCount++;
        return null;
    }
    int cmp;
    Entry<K,V> parent;
    // split comparator and comparable paths
    Comparator<? super K> cpr = comparator;
    if (cpr != null) {
        do {
            parent = t;
            cmp = cpr.compare(key, t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);
        } while (t != null);
    }
    else {
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
        Comparable<? super K> k = (Comparable<? super K>) key;
        do {
            parent = t;
            cmp = k.compareTo(t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);
        } while (t != null);
    }
    Entry<K,V> e = new Entry<>(key, value, parent);
    if (cmp < 0)
        parent.left = e;
    else
        parent.right = e;
    fixAfterInsertion(e);
    size++;
    modCount++;
    return null;
}

四、HashTable

HashTable虽然没有继承HashMap,它的实现方式与JDK1.7的HashMap几乎一致,存储采用数组+链表的形式,并不会像JDK1.8的HashMap那样有链表和红黑树的转换,所以实现上会简单很多

但是与HashMap相比,HashTable是一个线程安全的Map集合,它所有对外提供的方法都加了synchronized关键字进行同步

在HashTable的addEntry()方法中,如果需要进行扩容,会进行rehash,并且HashTable中计算数组下标的方式才是按照数组长度进行取模

最后采用头插法将元素插入到数组中

public synchronized V put(K key, V value) {
    // Make sure the value is not null
    if (value == null) {
        throw new NullPointerException();
    }

    // Makes sure the key is not already in the hashtable.
    Entry<?,?> tab[] = table;
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    @SuppressWarnings("unchecked")
    Entry<K,V> entry = (Entry<K,V>)tab[index];
    for(; entry != null ; entry = entry.next) {
        if ((entry.hash == hash) && entry.key.equals(key)) {
            V old = entry.value;
            entry.value = value;
            return old;
        }
    }

    addEntry(hash, key, value, index);
    return null;
}

private void addEntry(int hash, K key, V value, int index) {
    modCount++;

    Entry<?,?> tab[] = table;
    if (count >= threshold) {
        // Rehash the table if the threshold is exceeded
        rehash();

        tab = table;
        hash = key.hashCode();
        index = (hash & 0x7FFFFFFF) % tab.length;
    }

    // Creates the new entry. 头插法
    Entry<K,V> e = (Entry<K,V>) tab[index];
    tab[index] = new Entry<>(hash, key, value, e);
    count++;
}

五、ConcurrentHashMap

ConcurrentHashMap也是一个线程安全的Map集合,但它并不是像HashTable那样进行同步控制。在JDK1.7和JDK1.8中它们的实现有着本质的区别,JDK1.7采用分段锁来进行同步,而JDK1.8使用CAS+synchronized来进行同步,我们从源码来分析这两种实现

5.1 分段锁(JDK1.7)

5.1.1 存储结构

在JDK1.7中,每个元素对应的存储结构如下:

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

    HashEntry(int hash, K key, V value, HashEntry<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }
}

而在JDK1.7的ConcurrentHashMap中有一个非常重要的类Segment,这个类继承自ReentrantLock,具有加索的共功能。而这个Segment里面的属性结构与普通的HashMap很相似,也可以把Segment就看成HashMap的结构

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

    Segment(float lf, int threshold, HashEntry<K,V>[] tab) {
        this.loadFactor = lf;
        this.threshold = threshold;
        this.table = tab;
    }
    ……
}

在在ConcurrentHashMap中,有一个Segment数组,这个数组记录了所有元素对应的分组

final Segment<K,V>[] segments;
5.1.2 检索分段

在ConcurrentHashMap的put()方法中,会根据计算得到的hash值,然后计算KEY对应的分段,这里面用到了Unsafe类的本地方法,如果没有现成的分段,就调用ensureSegment()方法来生成一个分段

public V put(K key, V value) {
    Segment<K,V> s;
    if (value == null)
        throw new NullPointerException();
    int hash = hash(key);
    int j = (hash >>> segmentShift) & segmentMask;
    if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
         (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
        s = ensureSegment(j);
    return s.put(key, hash, value, false);
}

在ensureSegment()方法中,会把第一个分段的基本信息,HashEntry数组的容量、加载因子都拿出来,作为基础参数,生成一个新的Segment,然后利用CAS将新的Segment填充到数组中

private Segment<K,V> ensureSegment(int k) {
    final Segment<K,V>[] ss = this.segments;
    long u = (k << SSHIFT) + SBASE; // raw offset
    Segment<K,V> seg;
    if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
        Segment<K,V> proto = ss[0]; // use segment 0 as prototype
        int cap = proto.table.length;
        float lf = proto.loadFactor;
        int threshold = (int)(cap * lf);
        HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
        if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
            == null) { // recheck
            Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
            while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
                   == null) {
                if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
                    break;
            }
        }
    }
    return seg;
}
5.1.3 put方法实现

ConcurrentHashMap的put()方法中,获取到对应的Segment后,会去调用Segment类的put()方法

首先会调用tryLock()进行尝试加索,如果尝试失败,在调用scanAndLockForPut(),在scanAndLockForPut方法中也是先调用tryLock()进行尝试加锁,如果失败次数超过MAX_SCAN_RETRIES值后,直接调用lock()方法进行加锁

下面的代码就是正常的遍历查找,找不到就创建一个新的HashEntry,然后加入到当前分段的HashEntry数组中,这个数组也是才是通过数组+链表实现的,数组下标冲突时,采用头插法将元素添加到链表中

static final int MAX_SCAN_RETRIES =
    Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;

final V put(K key, int hash, V value, boolean onlyIfAbsent) {
    HashEntry<K,V> node = tryLock() ? null :
    scanAndLockForPut(key, hash, value);
    V oldValue;
    try {
        HashEntry<K,V>[] tab = table;
        int index = (tab.length - 1) & hash;
        HashEntry<K,V> first = entryAt(tab, index);
        for (HashEntry<K,V> e = first;;) {
            if (e != null) {
                K k;
                if ((k = e.key) == key ||
                    (e.hash == hash && key.equals(k))) {
                    oldValue = e.value;
                    if (!onlyIfAbsent) {
                        e.value = value;
                        ++modCount;
                    }
                    break;
                }
                e = e.next;
            }
            else {
                if (node != null)
                    node.setNext(first);
                else
                    node = new HashEntry<K,V>(hash, key, value, first);
                int c = count + 1;
                if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                    rehash(node);
                else
                    setEntryAt(tab, index, node);
                ++modCount;
                count = c;
                oldValue = null;
                break;
            }
        }
    } finally {
        unlock();
    }
    return oldValue;
}

5.2 CAS+synchronized(JDK1.8)

5.2.1 存储结构

JDK1.8中的ConcurrentHashMap与HashMap一样,采用数组+链表+红黑树的形式进行存储

transient volatile Node<K,V>[] table;

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    volatile V val;
    volatile Node<K,V> next;

    Node(int hash, K key, V val, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.val = val;
        this.next = next;
    }
}

static final class TreeNode<K,V> extends Node<K,V> {
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;

    TreeNode(int hash, K key, V val, Node<K,V> next,
             TreeNode<K,V> parent) {
        super(hash, key, val, next);
        this.parent = parent;
    }
}
5.2.2 put方法实现

同样根据hash值先计算出对应的数组的下标,如果当前数组的位置没有元素,则直接通过casTabAt()方法添加元素,如果此时有别的现成抢先一步把数组当前位置填充了值,casTabAt()方法就会返回false,那么下一次循环时,就会使用synchronized关键字对数组中进行同步,保证只有一个线程可以操作

final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
                break;                   // no lock when adding to empty bin
        }
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key,
                                                          value, null);
                                break;
                            }
                        }
                    }
                    else if (f instanceof TreeBin) {
                        Node<K,V> p;
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                              value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                }
            }
            if (binCount != 0) {
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    addCount(1L, binCount);
    return null;
}

添加完元素之后,需要判断链表是否需要转成红黑树,在转红黑树的时候,同样先取出数组上的元素,然后通过synchronized对其进行同步

if (binCount != 0) {
    if (binCount >= TREEIFY_THRESHOLD)
        treeifyBin(tab, i);
    if (oldVal != null)
        return oldVal;
    break;
}

private final void treeifyBin(Node<K,V>[] tab, int index) {
    Node<K,V> b; int n, sc;
    if (tab != null) {
        if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
            tryPresize(n << 1);
        else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
            synchronized (b) {
                if (tabAt(tab, index) == b) {
                    TreeNode<K,V> hd = null, tl = null;
                    for (Node<K,V> e = b; e != null; e = e.next) {
                        TreeNode<K,V> p =
                            new TreeNode<K,V>(e.hash, e.key, e.val,
                                              null, null);
                        if ((p.prev = tl) == null)
                            hd = p;
                        else
                            tl.next = p;
                        tl = p;
                    }
                    setTabAt(tab, index, new TreeBin<K,V>(hd));
                }
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值