Java8 容器類詳解


  ArrayList Vector CopyOnWriteArrayList LinkedList HashMap ConcurrentHashMap LinkedHashMap LinkedBlockingQueue PriorityQueue
使用場景 隨機訪問 ArrayList的線程安全版 讀多寫少,寫加鎖,寫操作在復制的數組上進行,會導致數據不一致,存在 添加刪除更快 映射 線程安全的HashMap 保證插入次序或者LRU次序的HashMap 鏈表實現的阻塞隊列 優先隊列,排序
底層實現 數組 數組 數組 雙向鏈表 數組+鏈表/紅黑樹(鏈表長度大於8時) 數組+鏈表/紅黑樹 數組+鏈表/紅黑樹,另外維護了一個雙向鏈表 雙向鏈表
擴容 1.5倍 2倍 每次add操作都會將數組拷貝到len+1長的新數組中 / 2倍(rehash)   2倍    
線程安全 否,可以使用Collections.synchronizedList()得到線程安全的List 是(synchronized) 是(ReentrantLock) 是(分段表/CAS+synchronized(jdk1.8之后)) 是(ReentrantLock)
Fail-Fast    

1.Fail-Fast使用 protected transient int modCount = 0; 支持,modCount記錄容器結構化修改次數。在操作的前后判斷modCount是否改變,若改變則認為序列化或者使用迭代其期間容器被結構化修改,則拋出異常。

2.HashMap中使用的技巧:

  • 求hash值:將key的hash值的高16位和低16位進行與運算,降低哈希碰撞
  • 取模:y%x => y&(x-1)
  • 擴容rehash計算桶下標:原容量的二進制位為0則位置不變,為1則位置+oldCap
  • 當用戶傳入制定容量時,找到大於等於容量的最近的二進制數

一、ArrayList

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

ArrayList主要實現了List、RandomAccess接口。

ArrayList是List接口的可變大小數組的實現。實現所有可選鏈表操作,允許所有元素,包括null。除了實現了List接口之外,這個類還提供了操作用來內部存儲鏈表的數組大小的方法(這個類大致等同於Vector,除了他是異步的以外)。

size()、isEmpty()、get()、set()、iterator()、listIterator()操作以恆定的時間運行。add()操作以O(n)的時間復雜度運行,其他的所有操作則以線性的時間運行(粗略的說)。常量因子相比與LinkedList較低。

每個ArrayList實例有一個capacity(容量)。capacity是用來存儲鏈表元素的數組的大小。它總是至少和list大小一樣大。當元素被加入到ArrayList后,它的capacity會自動擴容。除了增加一個元素有恆定的攤銷時間成本之外,增長策略的細節未被指定。

在添加大量的元素之前,一個應用可以使用ensureCapacity()操作提高一個實例的capacity。這可以減少增量重分配的數量(This may reduce the amount of incremental reallocation)。

要注意的是ArrayList不是同步的。如果多線程並發的訪問一個ArrayList實例,至少有一個線程會修改鏈表結構,必須在它的外部進行異步控制。(一個結構化的修改是指增加或刪除一個或多個元素的操作,或者顯式改變底層數組的大小;僅僅設置一個元素的值不是一個結構化的修改。)一個典型的實現是通過對某個對象的同步來自然封裝這個鏈表。(This is typically accomplished by synchronizing on some object that naturally encapsulates the list.)

如果沒有這種對象,這個鏈表則需要用Collections.synchronizedList()方法來封裝。這最好是在創建的時候完成,以防止偶然的對鏈表的異步訪問。

List list = Collections.synchronizedList(new ArrayList(...));

類的iterator()和listIterator(int)方法返回的迭代器都是fail-fast的:如果迭代器創建之后,鏈表在任意時刻被結構化的修改,除了迭代器自己的remove()、add()方法(迭代器自己的add()、remove()方法會將expectedModCount=modCount,因此不會觸發異常,具體見方法分析),迭代器將拋出ConcurrentModificationException異常。因此,面對並發修改,迭代器將快速失敗並清空,而不是在未來的不確定的時刻冒着任意的風險以及不確定的行為。

注意,迭代器的fail-fast行為不能被保證一定發生,通常來說,當異步並發修改發生時,不可能做出任何硬性保證。Fail-fast迭代器基於最大努力拋出ConcurrentModificationException異常。因此,這樣的做法的是錯的,寫一個程序,依賴於這個異常來保證程序的正確性:迭代器的fail-fast行為應該只被用來檢查bug(個人理解是如果發生並發修改,並不一定保證拋出ConcurrentModificationException異常,因此程序的正確性不應該依賴此異常)。

1.默認容量capacity

/**
 * Default initial capacity.
*/ private static final int DEFAULT_CAPACITY = 10;

2.存儲ArrayList元素的數組elementData

/**
 * The array buffer into which the elements of the ArrayList are stored.
 * The capacity of the ArrayList is the length of this array buffer. Any
 * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 * will be expanded to DEFAULT_CAPACITY when the first element is added.
 */
transient Object[] elementData; // non-private to simplify nested class access

ArrayList的容量就是數組的長度。

任何elementData等於DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList,當其第一個元素被添加進來時,elementData的長度將被擴展為DEFAULT_CAPACITY。

3.ArrayList包含元素的數量size

/**
 * The size of the ArrayList (the number of elements it contains).
 *
 * @serial
*/ private int size;

4.構造器

ArrayList包含三種構造器:

1.ArrayList():將elementData初始化為空數組DEFAULTCAPACITY_EMPTY_ELEMENTDATA。

2.ArrayList(int initialCapacity):將element初始化為容量為initialCapacity的數組,當initialCapacity為0是,初始化為EMPTY_ELEMENTDATA

3.ArrayList(Collection<? extends E> c):使用集合來初始化elementData,如果集合的length為0,則初始化為EMPTY_ELEMENTDATA。

    /**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // defend against c.toArray (incorrectly) not returning Object[]
            // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

5.Fail-fast與結構化修改

Fail-fast機制

ArrayList繼承於AbstractList,AbstractList中的域modCount用來統計結構化修改的次數。

結構化修改是指改變鏈表的大小,或者其他擾亂它的方式,可能導致正在進行的迭代可能產生不正確的結果。

    /**
     * The number of times this list has been <i>structurally modified</i>.
     * Structural modifications are those that change the size of the
     * list, or otherwise perturb it in such a fashion that iterations in
     * progress may yield incorrect results.
     *
     * <p>This field is used by the iterator and list iterator implementation
     * returned by the {@code iterator} and {@code listIterator} methods.
     * If the value of this field changes unexpectedly, the iterator (or list
     * iterator) will throw a {@code ConcurrentModificationException} in
     * response to the {@code next}, {@code remove}, {@code previous},
     * {@code set} or {@code add} operations.  This provides
     * <i>fail-fast</i> behavior, rather than non-deterministic behavior in
     * the face of concurrent modification during iteration.
     *
     * <p><b>Use of this field by subclasses is optional.</b> If a subclass
     * wishes to provide fail-fast iterators (and list iterators), then it
     * merely has to increment this field in its {@code add(int, E)} and
     * {@code remove(int)} methods (and any other methods that it overrides
     * that result in structural modifications to the list).  A single call to
     * {@code add(int, E)} or {@code remove(int)} must add no more than
     * one to this field, or the iterators (and list iterators) will throw
     * bogus {@code ConcurrentModificationExceptions}.  If an implementation
     * does not wish to provide fail-fast iterators, this field may be
     * ignored.
     */
    protected transient int modCount = 0;
View Code

在序列化以及迭代(forEach)等操作的前后,需要記錄modCount的值進行對比,如果不相等,則拋出ConcurrentModificationException異常。

    /**
     * Saves the state of the {@code ArrayList} instance to a stream
     * (that is, serializes it).
     *
     * @param s the stream
     * @throws java.io.IOException if an I/O error occurs
     * @serialData The length of the array backing the {@code ArrayList}
     *             instance is emitted (int), followed by all of its elements
     *             (each an {@code Object}) in the proper order.
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioral compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

    /**
     * @throws NullPointerException {@inheritDoc}
     */
    @Override
    public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        final int expectedModCount = modCount;
        final Object[] es = elementData;
        final int size = this.size;
        for (int i = 0; modCount == expectedModCount && i < size; i++)
            action.accept(elementAt(es, i));
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }

 

6.重要的方法

  • public void ensureCapacity(int minCapacity)

  參數minCapacity是要擴容的最小大小,要進行擴容必須滿足:

    1.minCapacity大於現在elementData的長度。

    2.不滿足(elementData為默認空數組&&要擴容的最小大小小於DEFAULT_CAPACITY

    /**
     * Increases the capacity of this {@code ArrayList} instance, if
     * necessary, to ensure that it can hold at least the number of elements
     * specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    public void ensureCapacity(int minCapacity) {
        if (minCapacity > elementData.length
            && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
                 && minCapacity <= DEFAULT_CAPACITY)) {
            modCount++;
            grow(minCapacity);
        }
    }
  • private int newCapacity(int minCapacity)

  返回至少和給定minCapacity一樣大小的容量。

  如果可以的話將返回elementData大小的1.5倍。

  除非給定minCapacity大於MAX_ARRAY_SIZE,否則容量不應超過MAX_ARRAY_SIZE。

  /**
     * Returns a capacity at least as large as the given minimum capacity.
     * Returns the current capacity increased by 50% if that suffices.
     * Will not return a capacity greater than MAX_ARRAY_SIZE unless
     * the given minimum capacity is greater than MAX_ARRAY_SIZE.
     *
     * @param minCapacity the desired minimum capacity
     * @throws OutOfMemoryError if minCapacity is less than zero
     */
    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity <= 0) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
          //之前介紹elementData的時候說到,elementData為DEFAULTCAPACITY_EMPTY_ELEMENTDATA
          //的空ArrayList,當第一次添加元素時,容量被擴充為DEFAULT_CAPACITY
return Math.max(DEFAULT_CAPACITY, minCapacity); if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return minCapacity; } return (newCapacity - MAX_ARRAY_SIZE <= 0) ? newCapacity : hugeCapacity(minCapacity); }
  • private Object[] grow(int minCapacity)
  • private Object[] grow()
    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     * @throws OutOfMemoryError if minCapacity is less than zero
     */
    private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    }

    private Object[] grow() {
        return grow(size + 1);
    }
  • private void add(E e, Object[] elementData, int s)
  • public boolean add(E e)
  • public add(int index, E element)

add(E e)執行時,根據調用關系,當要進行擴容時,最終會調用newCapacity(),容量為擴充為element.length*1.5和minCapacity中的較大者(通常情況是這樣,具體參見代碼newCapacity())。

add(int index, E element)執行時,如需進行擴容會先進行擴容,然后通過System.arraycopy進行移位,再將元素插入指定位置。

    /**
     * This helper method split out from add(E) to keep method
     * bytecode size under 35 (the -XX:MaxInlineSize default value),
     * which helps when add(E) is called in a C1-compiled loop.
     */
    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }

    /**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);
        modCount++;
        final int s;
        Object[] elementData;
        if ((s = size) == (elementData = this.elementData).length)
            elementData = grow();
        System.arraycopy(elementData, index,
                         elementData, index + 1,
                         s - index);
        elementData[index] = element;
        size = s + 1;
    }
  • public E remove(int index)
  • public boolean remove(Object o)
  • private void fastRemove(Object[] es, int i)

  remove(int index)主要調用了fastRemove()來進行刪除,fastRemove()用的也是System.arraycopy()。

  remove(Object o)先找到該對象的索引,然后調用fastRemove()。

    /**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     *
     * @param index the index of the element to be removed
     * @return the element that was removed from the list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        Objects.checkIndex(index, size);
        final Object[] es = elementData;

        @SuppressWarnings("unchecked") E oldValue = (E) es[index];
        fastRemove(es, index);

        return oldValue;
    }

    /**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If the list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * {@code i} such that
     * {@code Objects.equals(o, get(i))}
     * (if such an element exists).  Returns {@code true} if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return {@code true} if this list contained the specified element
     */
    public boolean remove(Object o) {
        final Object[] es = elementData;
        final int size = this.size;
        int i = 0;
        found: {
            if (o == null) {
                for (; i < size; i++)
                    if (es[i] == null)
                        break found;
            } else {
                for (; i < size; i++)
                    if (o.equals(es[i]))
                        break found;
            }
            return false;
        }
        fastRemove(es, i);
        return true;
    }

    /**
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(Object[] es, int i) {
        modCount++;
        final int newSize;
        if ((newSize = size - 1) > i)
            System.arraycopy(es, i + 1, es, i, newSize - i);
        es[size = newSize] = null;
    }
  • 迭代器的add()、remove(),通過對expectedModCount的同步,使得迭代器本身對鏈表的修改不會觸發異常。
    private class Itr implements Iterator<E> {
    ...

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    ...

    }

    private class ListItr extends Itr implements ListIterator<E> {

    ...
        public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                ArrayList.this.add(i, e);
                cursor = i + 1;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    ...

    }

7.序列化

ArrayList 基於數組實現,並且具有動態擴容特性,因此保存元素的數組不一定都會被使用,那么就沒必要全部進行序列化。

可以發現,elementData被transient修飾,不會被序列化。

transient 關鍵字

    /**
     * Saves the state of the {@code ArrayList} instance to a stream
     * (that is, serializes it).
     *
     * @param s the stream
     * @throws java.io.IOException if an I/O error occurs
     * @serialData The length of the array backing the {@code ArrayList}
     *             instance is emitted (int), followed by all of its elements
     *             (each an {@code Object}) in the proper order.
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioral compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

    /**
     * Reconstitutes the {@code ArrayList} instance from a stream (that is,
     * deserializes it).
     * @param s the stream
     * @throws ClassNotFoundException if the class of a serialized object
     *         could not be found
     * @throws java.io.IOException if an I/O error occurs
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {

        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // like clone(), allocate array based upon size not capacity
            SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size);
            Object[] elements = new Object[size];

            // Read in all elements in the proper order.
            for (int i = 0; i < size; i++) {
                elements[i] = s.readObject();
            }

            elementData = elements;
        } else if (size == 0) {
            elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new java.io.InvalidObjectException("Invalid size: " + size);
        }
    }

 二、Vector

public class Vector<E>
  extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable

Vector主要實現了List、RandomAccess接口。

Vector類實現了一個對象的可增長數組。如同一個數組,Vector包含可以使用整數索引訪問的component。然而,Vector被創建出來之后,其大小可以根據增加或者移除item來增長或者縮減。

每個Vector通過維持capacity和capacityIncrement來試圖優化存儲管理。capacity總是至少和Vector的size一樣大;通常,capacity會更大,因為當元素添加進Vector的時候,Vector的存儲空間會增長capacityIncrement的大小。在插入大量元素以前,應用會增長Vector的capacity;Vector減少了增長重分配的數量。

1.重要的域

  • elementData:存儲Vector組件(以下翻譯成元素)的數組elementData,最后一個元素之后的數組元素為null
    /**
     * The array buffer into which the components of the vector are
     * stored. The capacity of the vector is the length of this array buffer,
     * and is at least large enough to contain all the vector's elements.
     *
     * <p>Any array elements following the last element in the Vector are null.
     *
     * @serial
     */
    protected Object[] elementData;
  • elementCount:Vector對象合法元素的個數
    /**
     * The number of valid components in this {@code Vector} object.
     * Components {@code elementData[0]} through
     * {@code elementData[elementCount-1]} are the actual items.
     *
     * @serial
     */
    protected int elementCount;
  • capacityIncrement:當元素數量超過capacity時,capacity會自動增長capacityIncrement的大小。如果capacityIncrement小於等於0,capacit則y在每次需要增長時會擴大為兩倍。
    /**
     * The amount by which the capacity of the vector is automatically
     * incremented when its size becomes greater than its capacity.  If
     * the capacity increment is less than or equal to zero, the capacity
     * of the vector is doubled each time it needs to grow.
     *
     * @serial
     */
    protected int capacityIncrement;

3.重要的方法

    /**
     * Returns a capacity at least as large as the given minimum capacity.
     * Will not return a capacity greater than MAX_ARRAY_SIZE unless
     * the given minimum capacity is greater than MAX_ARRAY_SIZE.
     *
     * @param minCapacity the desired minimum capacity
     * @throws OutOfMemoryError if minCapacity is less than zero
     */
    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity <= 0) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }

 

4.Vector與ArrayList比較

  • Vector與ArrayList類似,但是Vector使用了synchronized進行同步。
  • 由於Vector時同步的,因此其開銷要比ArrayList更大,訪問速度更慢。最好使用ArrayList而不是Vector,因為同步操作完全可以由程序員自己來控制。
  • Vector每次擴容的空間是由capacityIncrement決定的,要么為oldCapacity+capacityIncrement,要么為原先的兩倍,ArrayList為原先的1.5倍。

5.替代方案

  • 在ArrayList部分中提到,可以使用Collections.synchronizedList()得到一個線程安全的ArrayList。
  • 可以使用concurrent並發包下的CopyOnWriteArrayList類。

三、CopyOnWriteArrayList

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable

CopyOnWriteArrayList主要實現了List、RandomAccess接口。

ArrayList線程安全的版本,所有的修改操作通過底層數組的一個拷貝來實現。

這通常會造成很大的開銷,但是當便利操作遠超於修改操作時,通常會更有效。並且當你不能或者不想同步遍歷這將會很有用,你需要排除並發帶來的干擾。“快照”風格的迭代器方法在迭代器創建時,使用數組狀態的一個引用。在迭代器的生命周期中,數組不會被改變,因此不會存在干擾且迭代器保證不會拋出ConcurrentModificationException。迭代器被創建之后,不會反映對鏈表的增、刪、改。迭代器自身的元素改變操作remove()、set()不被支持。這些方法會拋出UnsupportOperationException。

所有的元素都被允許,包括null。

存儲一致性效果:和其他並發集合一樣,向CopyOnWriteArrayList放置一個元素的線程發生在訪問或移除這個元素的其他線程之前。

1.重要的域

  • 鎖lock(對一些修改操作上鎖)
    /**
     * The lock protecting all mutators.  (We have a mild preference
     * for builtin monitors over ReentrantLock when either will do.)
     */
    final transient Object lock = new Object();
  • array
    /** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array;

2.重要的方法

修改的操作都進行了鎖,防止數據丟失等。

add()方法先getArray()取得數組,然后拷貝一份新數組進行修改,最后將底層數組設置為新數組。

同理,remove(int index)也是不是對原數組進行操作,而是使用一個新的數組。

寫操作在新的拷貝上進行,讀操作仍然讀取原來的數組,互不影響。

    public E set(int index, E element) {
        synchronized (lock) {
            Object[] es = getArray();
            E oldValue = elementAt(es, index);

            if (oldValue != element) {
                es = es.clone();
                es[index] = element;
                setArray(es);
            }
            return oldValue;
        }
    }

    public boolean add(E e) {
        synchronized (lock) {
            Object[] es = getArray();
            int len = es.length;
            es = Arrays.copyOf(es, len + 1);
            es[len] = e;
            setArray(es);
            return true;
        }
    }

    public void add(int index, E element) {
        synchronized (lock) {
            Object[] es = getArray();
            int len = es.length;
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException(outOfBounds(index, len));
            Object[] newElements;
            int numMoved = len - index;
            if (numMoved == 0)
                newElements = Arrays.copyOf(es, len + 1);
            else {
                newElements = new Object[len + 1];
                System.arraycopy(es, 0, newElements, 0, index);
                System.arraycopy(es, index, newElements, index + 1,
                                 numMoved);
            }
            newElements[index] = element;
            setArray(newElements);
        }
    }

    public E remove(int index) {
        synchronized (lock) {
            Object[] es = getArray();
            int len = es.length;
            E oldValue = elementAt(es, index);
            int numMoved = len - index - 1;
            Object[] newElements;
            if (numMoved == 0)
                newElements = Arrays.copyOf(es, len - 1);
            else {
                newElements = new Object[len - 1];
                System.arraycopy(es, 0, newElements, 0, index);
                System.arraycopy(es, index + 1, newElements, index,
                                 numMoved);
            }
            setArray(newElements);
            return oldValue;
        }
    }

3.適用場景

CopyOnWriteArrayList在寫操作的同時允許讀操作,大大提交了讀操作的性能,因此很適合讀多寫少的應用場景。

但是CopyOnWriteArrayList有其缺陷:

  • 內存占用:在寫操作時需要復制一個新的數組,使得內存占用為原來的兩倍左右。
  • 數據不一致:讀操作不能讀取實時性的數據,因為部分寫操作的數據還未同步到數組中。

所以CopyOnWriteArrayList不適合內存敏感以及對實時性要求很高的場景。

四、LinkedList

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

LinkedList主要實現了List、Deque(雙向鏈表)接口。

List和Deque接口的雙向鏈表實現。實現了所有鏈表的操作,允許所有類型包括null。

所有的操作表現為一個雙向鏈表所預期的那樣。索引鏈表的操作將從開始或結尾開始遍歷鏈表,這取決於開始和結尾哪個更靠近要索引的節點。

注意LinkedList不是同步的。如果在並發訪問下,並且至少有一個會結構化的修改鏈表,則必須在外部進行同步。關於結構化修改和之前所述一致。

LinkedList也可以使用Collections.synchronizedList(new LinkedList(...));來進行封裝,使得LinkedList支持並發訪問。

LinkedList的迭代器也遵循Fail-fast機制。

1.重要的域

  • transient Node<E> first;
  • transient Node<E> last;
    /**
     * Pointer to first node.
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     */
    transient Node<E> last;

2.重要的方法

LinkedList方法較為簡單,主要是對雙向鏈表進行操作。

    /**
     * Links e as first element.
     */
    private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

    /**
     * Links e as last element.
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

    /**
     * Inserts element e before non-null Node succ.
     */
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

    /**
     * Unlinks non-null first node f.
     */
    private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null; // help GC
        first = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

    /**
     * Unlinks non-null last node l.
     */
    private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        final E element = l.item;
        final Node<E> prev = l.prev;
        l.item = null;
        l.prev = null; // help GC
        last = prev;
        if (prev == null)
            first = null;
        else
            prev.next = null;
        size--;
        modCount++;
        return element;
    }

    /**
     * Unlinks non-null node x.
     */
    E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }
View Code
    /**
     * Returns the (non-null) Node at the specified element index.
     */
    Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
View Code

3.LinkedList和ArrayList的比較

  • ArrayList底層基於動態擴容的數組實現,LinkedList基於雙向鏈表實現。
  • ArrayList支持隨機訪問,LinkedList不支持。
  • LinkedList在任意位置添加刪除元素更快。

這些區別主要是基於數組和鏈表的特點。

五、HashMap

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

HashMap主要實現了Map接口。

Map接口基於HashTable的實現。HashMap提供了所有的map操作,允許null value和null key。HashMap類大致等同於HashTable類,除了HashMap是異步的且允許null。這個類不保證map的順序;特別的,它不保證順序隨着時間的推移會保持不變。

在哈希函數使元素正確分散的前提下,HashMap對於get()、put()操作提供恆定時間的表現(constant-time performance)。集合視圖的迭代需要與HashMap的capacity(bucket的數量)及size(鍵值對的數量)成正比的時間。因此,如果迭代性能很重要的話,不要把初始容量設的太高(或者加載因子設的太低)使很重要的。

HashMap實例有兩個參數影響其性能:

  • 初始容量
  • 加載因子

容量(capacity)是HashTable的桶(bucket)的數量,初始容量在創建時HashTable的容量。

加載因子(load factor)是衡量在capacity擴容之前,HashTable允許被填裝的多滿。

當HashTable中鍵值對個數超過加載因子和當前容量的乘積時,HashTable將會被rehash(即內部數據結構將重建),HashTable的桶將會擴大為近似兩倍的桶。

一般來說,默認加載因子(0.75)提供了對時間和空間較好的平衡。加載因子更高會減少空間開銷但會提升查找開銷(對照HashMap類大多數操作,包括get()、put())。在初始化容量的時候,預期的鍵值對數量和加載因子需要被考慮,來最小化rehash的次數。如果初始化容量大於鍵值對最大值除以加載因子,就不會發生rehash操作。

如果HashMap實例要存儲較多的鍵值對,用一個足夠大的容量來創建它將使得鍵值對更有效率的被存儲,相比於讓它自動rehash來增長。注意使用具有相同hashCode()的鍵會降低一些hash table的性能。為了改善影響,當鍵是可比較的,這個類將使用比較順序來打破這種關系。

注意,HashMap也不是同步的。可以使用Collections.synchronizedMap(new HashMap(...))來進行異步訪問。

1.構造函數

    /**
     * Constructs an empty {@code HashMap} with the specified initial
     * capacity and load factor.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    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);
    }

    /**
     * Constructs an empty {@code HashMap} with the specified initial
     * capacity and the default load factor (0.75).
     *
     * @param  initialCapacity the initial capacity.
     * @throws IllegalArgumentException if the initial capacity is negative.
     */
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * Constructs an empty {@code HashMap} with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

    /**
     * Constructs a new {@code HashMap} with the same mappings as the
     * specified {@code Map}.  The {@code HashMap} is created with
     * default load factor (0.75) and an initial capacity sufficient to
     * hold the mappings in the specified {@code Map}.
     *
     * @param   m the map whose mappings are to be placed in this map
     * @throws  NullPointerException if the specified map is null
     */
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

2.Node節點

    /**
     * Basic hash bin node, used for most entries.  (See below for
     * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
     */
    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;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

3.put操作

需要注意的是,從jdk1.8開始,新節點是插入在鏈表的尾部,此前是插在頭部。

  • putVal(...)
    /**
     * Implements Map.put and related methods.
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i;
        //若table為null或者大小為0,則初始化表
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //如果對應的桶為null
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        //若不為null
        else {
            HashMap.Node<K,V> e; K k;
            //如果為同一個key,則取得節點
            if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //若為TreeNode
            else if (p instanceof HashMap.TreeNode)
                e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            //否則遍歷鏈表
            else {
                for (int binCount = 0; ; ++binCount) {
                    //如果遍歷到結尾還沒找到相同的key,則將新node插入到鏈表尾部
                    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;
                }
            }
            //e可能要被覆蓋的節點的位置,如果插入到鏈表尾,e為null
            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;
    }

4.確定桶下標

HashMap中有許多操作要先確定一個key對應的桶的下表

  • 計算key的hash值

這里對h和h>>>16進行異或,而不用key的hashCode(),是因為由於HashMap的key的hash值最終是要和capacity進行與運算,當capacity值不大時,與運算實際有效的位數較少,這樣key.hashCode()得到的hash值有很大一部分沒有用上,散列到table上沖突的概率增大,(h = key.hashCode()) ^ (h >>> 16)將hash值的低16位變為低16和高16的異或,原來的高16位不變,這樣可以減少散列沖突。

至於 >> 是指右移,>>>是指無符號右移。

具體解釋看這里,膜拜大佬。

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

如果x為2的次方,則 (y%x) 等於 (y & x-1)。

令x = 1 << 4,即x為2的4次方:

x     : 00010000
x - 1 : 00001111

令一個數y與x-1做與運算,可以去除y的第4位以上的數:

y       : 10110010
x - 1   : 00001111
y&(x-1) : 00000010

得到的結果與y對x取模的結果是一樣的。

由於位運算的代價比求模運算小的多,因此在進行求模運算的時候用位運算替代能帶來更大的性能。

確定桶下標的最后一步是將key的hash值對桶個數取模:hash%capacity,如果能保證capacity為2的n次方,那么就可以將取模的操作轉換為位運算。

在上一節中,putVal()方法也使用了位運算來計算桶下標。

5.動態擴容

設 HashMap 的 table 長度為 M,需要存儲的鍵值對數量為 N,如果哈希函數滿足均勻性的要求,那么每條鏈表的長度大約為 N/M,因此平均查找次數的復雜度為 O(N/M)。

為了讓查找的成本降低,應該盡可能使得 N/M 盡可能小,因此需要保證 M 盡可能大,也就是說 table 要盡可能大。但如果M太大的話,會造成空間上的浪費,因此需要在時間和空間的開銷上找到平衡。HashMap 采用動態擴容來根據當前的 N 值來調整 M 值,使得空間效率和時間效率都能得到保證。

和擴容相關的參數主要有:capacity、size、threshold 和 loadFactor。

參數 含義
capacity table 的容量大小,默認為 16。需要注意的是 capacity 必須保證為 2 的 n 次方。
size 鍵值對數量。
threshold size 的臨界值,當 size 大於等於 threshold 就必須進行擴容操作。
loadFactor 裝載因子,table 能夠使用的比例,threshold = capacity * loadFactor。
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

static final int MAXIMUM_CAPACITY = 1 << 30;

static final float DEFAULT_LOAD_FACTOR = 0.75f;

transient Node<K,V>[] table;

transient int size;

int threshold;

final float loadFactor;

擴容使用resize()方法實現:

    /**
     * Initializes or doubles table size.  If null, allocates in
     * accord with initial capacity target held in field threshold.
     * Otherwise, because we are using power-of-two expansion, the
     * elements from each bin must either stay at same index, or move
     * with a power of two offset in the new table.
     * 初始化或者將table大小變為兩倍。
     * 如果table為null,則根據threshold字段保存的初始容量來分配。
     * 如果table不為null,我們將用二次冪來擴展,每個桶的元素必須在相同索引的位置,或者在新表中
     * 移動二次冪的偏移。
     *
     * @return the table
     */
    final HashMap.Node<K,V>[] resize() {
        HashMap.Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        //如果原來容量大於0,則分配為原來的兩倍
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                    oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        //如果原來容量為0,原來的閾值不為0,則為第一次初始化table,新的容量為原來的閾值
        //使用帶參的構造函數走的是這個分支
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        //如果原來的容量為0,原來閾值也為0,則用默認值來初始化新的容量和閾值
        //使用HashMap不帶參的構造函數走的就是這個分支
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        //第二個分支沒有設置到newThr,在這里重新計算
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                    (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        //使用新的容量定義新的table
        @SuppressWarnings({"rawtypes","unchecked"})
        HashMap.Node<K,V>[] newTab = (HashMap.Node<K,V>[])new HashMap.Node[newCap];
        table = newTab;
        //遍歷舊表,將舊表中元素移到新表中
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                HashMap.Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    //若當前桶只有一個元素,則直接進行rehash
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof HashMap.TreeNode)
                        ((HashMap.TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        HashMap.Node<K,V> loHead = null, loTail = null;
                        HashMap.Node<K,V> hiHead = null, hiTail = null;
                        HashMap.Node<K,V> next;
                        do {
                            next = e.next;
                            //如果哈希值和oldCap做與運算為0,則在新表中還在原位
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            //若與運算不為0,則在新表中的位置為原索引+oldCap,可以舉例驗證
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

5.1擴容-重新計算桶下標

重新計算桶下標的邏輯也在resize()方法中

//如果哈希值和oldCap做與運算為0,則在新表中還在原位
if ((e.hash & oldCap) == 0) {
   if (loTail == null)
       loHead = e;
   else
       loTail.next = e;
       loTail = e;
}
//若與運算不為0,則在新表中的位置為原索引+oldCap
else {
    if (hiTail == null)
        hiHead = e;
    else
        hiTail.next = e;
        hiTail = e;
}
i,f (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}

在進行擴容時,需要把鍵值對重新放到對應的桶上。HashMap 使用了一個特殊的機制,可以降低重新計算桶下標的操作。

假設原數組長度 capacity 為 16,擴容之后 new capacity 為 32:

capacity     : 00010000
new capacity : 00100000

對於一個 Key,

  • 它的哈希值如果在第 5 位上為 0,那么取模得到的結果和之前一樣;
  • 如果為 1,那么得到的結果為原來的結果 +16。

6.計算數組容量

HashMap要求容量capacity為2的n次方,構造函數允許用戶傳入的容量不是2的n次方,因為它可以自動地將傳入的容量轉換為2的n次方。

    /**
     * Returns a power of two size for the given target capacity.
     */
    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;
    }

具體請看這里

7.鏈表轉紅黑樹

從 JDK 1.8 開始,一個桶存儲的鏈表長度大於 8 時會將鏈表轉換為紅黑樹。

8.HashMap與HashTable比較

  • HashTable使用synchronized來進行同步。
  • HashMap可以插入鍵為null的Entry。
  • HashMap的迭代器是Fail-fast迭代器。
  • HashMap不能保證隨着時間的推移Map中的元素次序是不變的。

六、ConcurrentHashMap

public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
    implements ConcurrentMap<K,V>, Serializable

支持檢索的全並發和更新的高預期並發的hash table。ConcurrentHashMap遵循與Hashtable相同的功能,包括Hashtable的每個方法的版本。然而,即使所有的操作是線程安全的,檢索操作也不意味着上鎖,ConcurrentHashMap對於對整個表上鎖來保護所有的訪問沒有任何的支持。在依賴線程安全但不依賴於它的同步細節的程序中,ConcurrentHashMap完全與Hashtable互操作。

檢索操作(包括get)通常不加鎖,所以可能會和更新操作(包括put、remove)重疊。檢索反應大多數近期完成的更新操作的結果(更加正式的說,檢索更新的值只與在這之前發生的更新操作有關)。對於聚合操作(putAll、clear),並發檢索可能只反映一部分插入和移除。相似的,迭代器、分割器、枚舉只反映它們創建時hashtable的狀態。它們不會拋出ConcurrentModificationException。然而迭代器被設計只用來一個時間只在一個線程中使用。記住聚合狀態方法(size、isEmpty、containsValue)的結果只有當map不會在其他線程中並發更新才有用。否則這些方法的結果反應的短暫狀態只適用於監視或者估計,而不適用於程序控制。

當有許多沖突時,ConcurrentHashMap會自動擴張(有不同hashcode的key卻掉進相同的插槽)。

1.put操作

    /**
     * Maps the specified key to the specified value in this table.
     * Neither the key nor the value can be null.
     *
     * <p>The value can be retrieved by calling the {@code get} method
     * with a key that is equal to the original key.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with {@code key}, or
     *         {@code null} if there was no mapping for {@code key}
     * @throws NullPointerException if the specified key or value is null
     */
    public V put(K key, V value) {
        return putVal(key, value, false);
    }

    /** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        //獲取key的hash
        int hash = spread(key.hashCode());
        //記錄鏈表長度
        int binCount = 0;
        for (ConcurrentHashMap.Node<K,V>[] tab = table;;) {
            ConcurrentHashMap.Node<K,V> f; int n, i, fh;
            //如果還未初始化table
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            //如果對應的桶為空
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                //用CAS操作插入節點,如果失敗則重試
                if (casTabAt(tab, i, null,
                        new ConcurrentHashMap.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);
            //如果table已經初始化完畢,且當前桶已經有數據了,且不在擴容。f為桶中的頭節點
            else {
                V oldVal = null;
                //對頭節點加鎖
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        //頭節點hash值大於等於0,說明是鏈表
                        if (fh >= 0) {
                            binCount = 1;
                            for (ConcurrentHashMap.Node<K,V> e = f;; ++binCount) {
                                K ek;
                                //如果發現了相等的key則覆蓋
                                if (e.hash == hash &&
                                        ((ek = e.key) == key ||
                                                (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                ConcurrentHashMap.Node<K,V> pred = e;
                                //如果遍歷到結尾,則將新值插入到鏈表尾
                                if ((e = e.next) == null) {
                                    pred.next = new ConcurrentHashMap.Node<K,V>(hash, key,
                                            value, null);
                                    break;
                                }
                            }
                        }
                        //紅黑樹
                        else if (f instanceof ConcurrentHashMap.TreeBin) {
                            ConcurrentHashMap.Node<K,V> p;
                            binCount = 2;
                            if ((p = ((ConcurrentHashMap.TreeBin<K,V>)f).putTreeVal(hash, key,
                                    value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
         
if (binCount != 0) { //如果binCount大於閾值 if (binCount >= TREEIFY_THRESHOLD) //這個方法和HashMap有點不同,它不是一定會進行紅黑樹轉化 //如果當前數組的長度小於64,則會選擇進行擴容 treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } addCount(1L, binCount); return null; }

 2.initTable

    /**
     * Initializes table, using the size recorded in sizeCtl.
     */
    private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
        while ((tab = table) == null || tab.length == 0) {
            //已有其他的線程在初始化或擴容
            if ((sc = sizeCtl) < 0)
                Thread.yield(); // lost initialization race; just spin
                //CAS一下,將sizeCtl設置為-1,代表搶到了鎖
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                    if ((tab = table) == null || tab.length == 0) {
                        //初始化數組,長度為16或為構造函數傳入的長度
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        //初始化table
                        table = tab = nt;
                        //0.75 * n
                        sc = n - (n >>> 2);
                    }
                } finally {
                    //設置sizeCtl為sc,默認情況下是12
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }

3.treeifyBin

當鏈表長度大於給定值時,將鏈表轉化為紅黑樹,但如果table的長度未達到MIN_TREEIFY_CAPACITY時,嘗試擴容而不是樹化。

    /**
     * Replaces all linked nodes in bin at given index unless table is
     * too small, in which case resizes instead.
     */
    private final void treeifyBin(Node<K,V>[] tab, int index) {
        Node<K,V> b; int n, sc;
        if (tab != null) {
            //當table長度小於MIN_TREEIFY_CAPACITY時,嘗試擴容
            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));
                    }
                }
            }
        }
    }

4.tryPresize

嘗試擴容table來容納給定的元素數量

    /**
     * Tries to presize table to accommodate the given number of elements.
     *
     * @param size number of elements (doesn't need to be perfectly accurate)
     */
    private final void tryPresize(int size) {
        int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
                tableSizeFor(size + (size >>> 1) + 1);
        int sc;
        //當sizeCtl大於0,代表下次擴容的大小
        while ((sc = sizeCtl) >= 0) {
            Node<K,V>[] tab = table; int n;
            //初始化代碼?
            if (tab == null || (n = tab.length) == 0) {
                n = (sc > c) ? sc : c;
                if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                    try {
                        if (table == tab) {
                            @SuppressWarnings("unchecked")
                            Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                            table = nt;
                            sc = n - (n >>> 2);
                        }
                    } finally {
                        sizeCtl = sc;
                    }
                }
            }
            //若擴容的大小還沒有達到下次擴容的閾值或者已經大於MAXIMUM_CAPACITY
            else if (c <= sc || n >= MAXIMUM_CAPACITY)
                break;
            //進行擴容和數據遷移
            else if (tab == table) {
                int rs = resizeStamp(n);
                if (sc < 0) {
                    Node<K,V>[] nt;
                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                            sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                            transferIndex <= 0)
                        break;
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                        transfer(tab, nt);
                }
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                        (rs << RESIZE_STAMP_SHIFT) + 2))
                    //傳入空值,在transfer中進行初始化,將table擴容為兩倍大
                    transfer(tab, null);
            }
        }
    }

5.transfer

    /**
     * Moves and/or copies the nodes in each bin to new table. See
     * above for explanation.
     */
    private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        int n = tab.length, stride;
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE; // subdivide range
        if (nextTab == null) {            // initiating
            try {
                @SuppressWarnings("unchecked")
                Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
                nextTab = nt;
            } catch (Throwable ex) {      // try to cope with OOME
                sizeCtl = Integer.MAX_VALUE;
                return;
            }
            nextTable = nextTab;
            transferIndex = n;
        }
        int nextn = nextTab.length;
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
        boolean advance = true;
        boolean finishing = false; // to ensure sweep before committing nextTab
        for (int i = 0, bound = 0;;) {
            Node<K,V> f; int fh;
            while (advance) {
                int nextIndex, nextBound;
                if (--i >= bound || finishing)
                    advance = false;
                else if ((nextIndex = transferIndex) <= 0) {
                    i = -1;
                    advance = false;
                }
                else if (U.compareAndSwapInt
                         (this, TRANSFERINDEX, nextIndex,
                          nextBound = (nextIndex > stride ?
                                       nextIndex - stride : 0))) {
                    bound = nextBound;
                    i = nextIndex - 1;
                    advance = false;
                }
            }
            if (i < 0 || i >= n || i + n >= nextn) {
                int sc;
                if (finishing) {
                    nextTable = null;
                    table = nextTab;
                    sizeCtl = (n << 1) - (n >>> 1);
                    return;
                }
                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                        return;
                    finishing = advance = true;
                    i = n; // recheck before commit
                }
            }
            else if ((f = tabAt(tab, i)) == null)
                advance = casTabAt(tab, i, null, fwd);
            else if ((fh = f.hash) == MOVED)
                advance = true; // already processed
            else {
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        Node<K,V> ln, hn;
                        if (fh >= 0) {
                            int runBit = fh & n;
                            Node<K,V> lastRun = f;
                            for (Node<K,V> p = f.next; p != null; p = p.next) {
                                int b = p.hash & n;
                                if (b != runBit) {
                                    runBit = b;
                                    lastRun = p;
                                }
                            }
                            if (runBit == 0) {
                                ln = lastRun;
                                hn = null;
                            }
                            else {
                                hn = lastRun;
                                ln = null;
                            }
                            for (Node<K,V> p = f; p != lastRun; p = p.next) {
                                int ph = p.hash; K pk = p.key; V pv = p.val;
                                if ((ph & n) == 0)
                                    ln = new Node<K,V>(ph, pk, pv, ln);
                                else
                                    hn = new Node<K,V>(ph, pk, pv, hn);
                            }
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                        else if (f instanceof TreeBin) {
                            TreeBin<K,V> t = (TreeBin<K,V>)f;
                            TreeNode<K,V> lo = null, loTail = null;
                            TreeNode<K,V> hi = null, hiTail = null;
                            int lc = 0, hc = 0;
                            for (Node<K,V> e = t.first; e != null; e = e.next) {
                                int h = e.hash;
                                TreeNode<K,V> p = new TreeNode<K,V>
                                    (h, e.key, e.val, null, null);
                                if ((h & n) == 0) {
                                    if ((p.prev = loTail) == null)
                                        lo = p;
                                    else
                                        loTail.next = p;
                                    loTail = p;
                                    ++lc;
                                }
                                else {
                                    if ((p.prev = hiTail) == null)
                                        hi = p;
                                    else
                                        hiTail.next = p;
                                    hiTail = p;
                                    ++hc;
                                }
                            }
                            ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
                                (hc != 0) ? new TreeBin<K,V>(lo) : t;
                            hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
                                (lc != 0) ? new TreeBin<K,V>(hi) : t;
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                    }
                }
            }
        }
    }

七、LinkedHashMap

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>

LinkedHashMap繼承於HashMap,內部維護了一個雙向鏈表,用來維護插入順序或者LRU順序。

    /**
     * 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;

1.向鏈表中追加新節點

當向LinkedHashMap中插入新節點時,linkNodeLast()實現了向雙向鏈表中增加新節點,它在newNode()中被調用。

    // link at the end of list
    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;
        }
    }
    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;
    }

2.從鏈表中刪除節點

當從LinkedListMap移除節點時,afterNodeRemoval()實現從雙向鏈表中移除節點。

    void afterNodeRemoval(Node<K,V> e) { // unlink
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        p.before = p.after = null;
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a == null)
            tail = b;
        else
            a.before = b;
    }

3.accessOrder

accessOrder決定了LinkedHashMap雙向鏈表所維護的順序,默認為false,此時維護的是插入順序,當為true時,維護的是訪問順序(當訪問了LinkedHashMap的某個節點,則節點被刷新為youngest)。

 

4.訪問順序

afterNodeAccess()

當一個節點被訪問時,如果 accessOrder 為 true,則會將該節點移到鏈表尾部。也就是說指定為 LRU 順序之后,在每次訪問一個節點時,會將這個節點移到鏈表尾部,保證鏈表尾部是最近訪問的節點,那么鏈表首部就是最近最久未使用的節點。

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

afterNodeInsertion()

在 put 等操作之后執行,當 removeEldestEntry() 方法返回 true 時會移除最晚的節點,也就是鏈表首部節點 first。

evict 只有在構建 Map 的時候才為 false,在這里為 true。

void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}

removeEldestEntry() 默認為 false,如果需要讓它為 true,需要繼承 LinkedHashMap 並且覆蓋這個方法的實現,這在實現 LRU 的緩存中特別有用,通過移除最近最久未使用的節點,從而保證緩存空間足夠,並且緩存的數據都是熱點數據。

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}

4.LRU 緩存

以下是使用 LinkedHashMap 實現的一個 LRU 緩存:

  • 設定最大緩存空間 MAX_ENTRIES 為 3;
  • 使用 LinkedHashMap 的構造函數將 accessOrder 設置為 true,開啟 LRU 順序;
  • 覆蓋 removeEldestEntry() 方法實現,在節點多於 MAX_ENTRIES 就會將最近最久未使用的數據移除。
class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private static final int MAX_ENTRIES = 3;

    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > MAX_ENTRIES;
    }

    LRUCache() {
        super(MAX_ENTRIES, 0.75f, true);
    }
}
public static void main(String[] args) {
    LRUCache<Integer, String> cache = new LRUCache<>();
    cache.put(1, "a");
    cache.put(2, "b");
    cache.put(3, "c");
    cache.get(1);
    cache.put(4, "d");
    System.out.println(cache.keySet());
}
[3, 1, 4]

八、Hashtable

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable

Hashtable是線程安全的,使用synchronized進行控制。只能使用非空的key或者value。

為了成功的存儲和索引hashtable,作為key的對象必須實現hashcode()和equals()。

底層使用Entry數組來存儲

    /**
     * The hash table data.
     */
    private transient Entry<?,?>[] table;

兩個重要參數:threshold和loadFactor

1.put操作

從代碼中看出hashtable的key的hash值使用的是key.hashCode(),如果key只存在則替換,若不存在則新增。

    /**
     * Maps the specified <code>key</code> to the specified
     * <code>value</code> in this hashtable. Neither the key nor the
     * value can be <code>null</code>. <p>
     *
     * The value can be retrieved by calling the <code>get</code> method
     * with a key that is equal to the original key.
     *
     * @param      key     the hashtable key
     * @param      value   the value
     * @return     the previous value of the specified key in this hashtable,
     *             or <code>null</code> if it did not have one
     * @exception  NullPointerException  if the key or value is
     *               <code>null</code>
     * @see     Object#equals(Object)
     * @see     #get(Object)
     */
    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.
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>) tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
    }

容量擴大為原來的兩倍+1

    protected void rehash() {
        int oldCapacity = table.length;
        Entry<?,?>[] oldMap = table;

        // overflow-conscious code
        int newCapacity = (oldCapacity << 1) + 1;
        if (newCapacity - MAX_ARRAY_SIZE > 0) {
            if (oldCapacity == MAX_ARRAY_SIZE)
                // Keep running with MAX_ARRAY_SIZE buckets
                return;
            newCapacity = MAX_ARRAY_SIZE;
        }
        Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

        modCount++;
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        table = newMap;

        for (int i = oldCapacity ; i-- > 0 ;) {
            for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
                Entry<K,V> e = old;
                old = old.next;

                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = (Entry<K,V>)newMap[index];
                newMap[index] = e;
            }
        }
    }

2.remove操作

    public synchronized V remove(Object key) {
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>)tab[index];
        for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                modCount++;
                if (prev != null) {
                    prev.next = e.next;
                } else {
                    tab[index] = e.next;
                }
                count--;
                V oldValue = e.value;
                e.value = null;
                return oldValue;
            }
        }
        return null;
    }

3.get操作

    public synchronized V get(Object key) {
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return (V)e.value;
            }
        }
        return null;
    }

 九、TreeMap

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

基於紅黑樹的NavigableMap的實現,map根據key的Comparale自然順序排序,或者根據創建時候提供的Comparator,這取決於用什么構造函數創建。

注意TreeMap使用一個樹映射來維持排序,類似於其他的排序映射,不管是否有一個顯式comparator被提供,只要是要正確的實現Map接口,必須與equals()返回結果一致。

1.put操作

與root值進行比較,大於root則將值插在右子樹,小於則插在左子樹

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

2.get操作

和put是同樣的原理

    final Entry<K,V> getEntry(Object key) {
        // Offload comparator-based version for sake of performance
        if (comparator != null)
            return getEntryUsingComparator(key);
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
        Entry<K,V> p = root;
        while (p != null) {
            int cmp = k.compareTo(p.key);
            if (cmp < 0)
                p = p.left;
            else if (cmp > 0)
                p = p.right;
            else
                return p;
        }
        return null;
    }

3.getFloorEntry

返回小於key的最大節點

final Entry<K,V> getFloorEntry(K key) {
        Entry<K,V> p = root;
        while (p != null) {
            int cmp = compare(key, p.key);
            if (cmp > 0) {
                if (p.right != null)
                    p = p.right;
                else
                    return p;
            } else if (cmp < 0) {
                if (p.left != null) {
                    p = p.left;
                } else {
                    Entry<K,V> parent = p.parent;
                    Entry<K,V> ch = p;
                    while (parent != null && ch == parent.left) {
                        ch = parent;
                        parent = parent.parent;
                    }
                    return parent;
                }
            } else
                return p;

        }
        return null;
    }

 Collections.synchronizedList()

實現原理:不是線程安全的集合,通過Collections.synchronizedList()函數封裝一層,內部通過synchronized對一個Object對象進行同步,來實現線程安全。

 

參考文獻:

1.GitHub-CyC2018/CS-Notes

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM