大數據成神之路-Java高級特性加強(CopyOnWriteArrayList)

請戳GitHub原文: github.com/wangzhiwubi…java

大數據成神之路系列:

請戳GitHub原文: github.com/wangzhiwubi…git

Java高級特性加強-集合github

Java高級特性加強-多線程面試

Java高級特性加強-Synchronized數組

Java高級特性加強-volatile安全

Java高級特性加強-併發集合框架bash

Java高級特性加強-分佈式網絡

Java高級特性加強-Zookeeper數據結構

Java高級特性加強-JVM多線程

Java高級特性加強-NIO

Java高級特性加強-併發容器

本部分網絡上有大量的資源能夠參考,在這裏作了部分整理並作了大量勘誤,感謝前輩的付出,每節文章末尾有引用列表~ ####多線程 ###集合框架 ###NIO ###Java併發容器


概要

本章是"併發容器"的CopyOnWriteArrayList篇。接下來,會先對CopyOnWriteArrayList進行基本介紹,而後再說明它的原理,接着經過代碼去分析,最後經過示例更進一步的瞭解CopyOnWriteArrayList。內容包括:

  • CopyOnWriteArrayList介紹
  • CopyOnWriteArrayList原理和數據結構
  • CopyOnWriteArrayList函數列表
  • CopyOnWriteArrayList源碼分析(JDK1.7.0_40版本)
  • CopyOnWriteArrayList示例

CopyOnWriteArrayList介紹

它至關於線程安全的ArrayList。和ArrayList同樣,它是個可變數組;可是和ArrayList不一樣的時,它具備如下特性:

  1. 它最適合於具備如下特徵的應用程序:List 大小一般保持很小,只讀操做遠多於可變操做,須要在遍歷期間防止線程間的衝突。
  2. 它是線程安全的。
  3. 由於一般須要複製整個基礎數組,因此可變操做(add()、set() 和 remove() 等等)的開銷很大。
  4. 迭代器支持hasNext(), next()等不可變操做,但不支持可變 remove()等操做。
  5. 使用迭代器進行遍歷的速度很快,而且不會與其餘線程發生衝突。在構造迭代器時,迭代器依賴於不變的數組快照。 建議:在學習CopyOnWriteArraySet以前,先對ArrayList進行了解!

CopyOnWriteArrayList原理和數據結構

CopyOnWriteArrayList的數據結構,以下圖所示:

8c9a554d0e197a027a35fe2f0d483b81.jpeg
說明: 1.CopyOnWriteArrayList實現了List接口,所以它是一個隊列。 2.CopyOnWriteArrayList包含了成員lock。每個CopyOnWriteArrayList都和一個互斥鎖lock綁定,經過lock,實現了對CopyOnWriteArrayList的互斥訪問。 3. CopyOnWriteArrayList包含了成員array數組,這說明CopyOnWriteArrayList本質上經過數組實現的。 下面從「動態數組」和「線程安全」兩個方面進一步對CopyOnWriteArrayList的原理進行說明。 1.  CopyOnWriteArrayList的「動態數組」機制 -- 它內部有個「volatile數組」(array)來保持數據。在「添加/修改/刪除」數據時,都會新建一個數組,並將更新後的數據拷貝到新建的數組中,最後再將該數組賦值給「volatile數組」。這就是它叫作CopyOnWriteArrayList的緣由!CopyOnWriteArrayList就是經過這種方式實現的動態數組;不過正因爲它在「添加/修改/刪除」數據時,都會新建數組,因此涉及到修改數據的操做,CopyOnWriteArrayList效率很低;可是單單只是進行遍歷查找的話,效率比較高。 2.  CopyOnWriteArrayList的「線程安全」機制 -- 是經過volatile和互斥鎖來實現的。(01) CopyOnWriteArrayList是經過"volatile數組"來保存數據的。一個線程讀取volatile數組時,總能看到其它線程對該volatile變量最後的寫入;就這樣,經過volatile提供了"讀取到的數據老是最新的"這個機制的保證。(02) CopyOnWriteArrayList經過互斥鎖來保護數據。在"添加/修改/刪除"數據時,會先"獲取互斥鎖",再修改完畢以後,先將數據更新到「volatile數組」中,而後再"釋放互斥鎖",這樣,就達到了保護數據的目的。

CopyOnWriteArrayList函數列表

// 建立一個空列表。
CopyOnWriteArrayList()
// 建立一個按 collection 的迭代器返回元素的順序包含指定 collection 元素的列表。
CopyOnWriteArrayList(Collection<? extends E> c)
// CopyOnWriteArrayList(E[] toCopyIn)
建立一個保存給定數組的副本的列表。
// 將指定元素添加到此列表的尾部。
boolean add(E e)
// 在此列表的指定位置上插入指定元素。
void add(int index, E element)
// 按照指定 collection 的迭代器返回元素的順序,將指定 collection 中的全部元素添加此列表的尾部。
boolean addAll(Collection<? extends E> c)
// 從指定位置開始,將指定 collection 的全部元素插入此列表。
boolean addAll(int index, Collection<? extends E> c)
// 按照指定 collection 的迭代器返回元素的順序,將指定 collection 中還沒有包含在此列表中的全部元素添加列表的尾部。
int addAllAbsent(Collection<? extends E> c)
// 添加元素(若是不存在)。
boolean addIfAbsent(E e)
// 今後列表移除全部元素。
void clear()
// 返回此列表的淺表副本。
Object clone()
// 若是此列表包含指定的元素,則返回 true。
boolean contains(Object o)
// 若是此列表包含指定 collection 的全部元素,則返回 true。
boolean containsAll(Collection<?> c)
// 比較指定對象與此列表的相等性。
boolean equals(Object o)
// 返回列表中指定位置的元素。
E get(int index)
// 返回此列表的哈希碼值。
int hashCode()
// 返回第一次出現的指定元素在此列表中的索引,從 index 開始向前搜索,若是沒有找到該元素,則返回 -1。
int indexOf(E e, int index)
// 返回此列表中第一次出現的指定元素的索引;若是此列表不包含該元素,則返回 -1。
int indexOf(Object o)
// 若是此列表不包含任何元素,則返回 true。
boolean isEmpty()
// 返回以恰當順序在此列表元素上進行迭代的迭代器。
Iterator<E> iterator()
// 返回最後一次出現的指定元素在此列表中的索引,從 index 開始向後搜索,若是沒有找到該元素,則返回 -1。
int lastIndexOf(E e, int index)
// 返回此列表中最後出現的指定元素的索引;若是列表不包含此元素,則返回 -1。
int lastIndexOf(Object o)
// 返回此列表元素的列表迭代器(按適當順序)。
ListIterator<E> listIterator()
// 返回列表中元素的列表迭代器(按適當順序),從列表的指定位置開始。
ListIterator<E> listIterator(int index)
// 移除此列表指定位置上的元素。
E remove(int index)
// 今後列表移除第一次出現的指定元素(若是存在)。
boolean remove(Object o)
// 今後列表移除全部包含在指定 collection 中的元素。
boolean removeAll(Collection<?> c)
// 只保留此列表中包含在指定 collection 中的元素。
boolean retainAll(Collection<?> c)
// 用指定的元素替代此列表指定位置上的元素。
E set(int index, E element)
// 返回此列表中的元素數。
int size()
// 返回此列表中 fromIndex(包括)和 toIndex(不包括)之間部分的視圖。
List<E> subList(int fromIndex, int toIndex)
// 返回一個按恰當順序(從第一個元素到最後一個元素)包含此列表中全部元素的數組。
Object[] toArray()
// 返回以恰當順序(從第一個元素到最後一個元素)包含列表全部元素的數組;返回數組的運行時類型是指定數組的運行時類型。
<T> T[] toArray(T[] a)
// 返回此列表的字符串表示形式。
String toString()
複製代碼

CopyOnWriteArrayList源碼分析

下面咱們從"建立,添加,刪除,獲取,遍歷"這5個方面去分析CopyOnWriteArrayList的原理。 1. 建立 CopyOnWriteArrayList共3個構造函數。它們的源碼以下:

public CopyOnWriteArrayList() {
    setArray(new Object[0]);
}

public CopyOnWriteArrayList(Collection<? extends E> c) {
    Object[] elements = c.toArray();
    if (elements.getClass() != Object[].class)
        elements = Arrays.copyOf(elements, elements.length, Object[].class);
    setArray(elements);
}

public CopyOnWriteArrayList(E[] toCopyIn) {
    setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
複製代碼

說明:這3個構造函數都調用了setArray(),setArray()的源碼以下:

private volatile transient Object[] array;
final Object[] getArray() {
    return array;
}
final void setArray(Object[] a) {
    array = a;
}
複製代碼

說明:setArray()的做用是給array賦值;其中,array是volatile transient Object[]類型,即array是「volatile數組」。關於volatile關鍵字,咱們知道「volatile能讓變量變得可見」,即對一個volatile變量的讀,老是能看到(任意線程)對這個volatile變量最後的寫入。正在因爲這種特性,每次更新了「volatile數組」以後,其它線程都能看到對它所作的更新。關於transient關鍵字,它是在序列化中才起做用,transient變量不會被自動序列化。 2. 添加 以add(E e)爲例,來對"CopyOnWriteArrayList"的添加操做進行說明。下面是add(E e)的代碼:

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    // 獲取「鎖」
    lock.lock();
    try {
        // 獲取原始」volatile數組「中的數據和數據長度。
        Object[] elements = getArray();
        int len = elements.length;
        // 新建一個數組newElements,並將原始數據拷貝到newElements中;
        // newElements數組的長度=「原始數組的長度」+1
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        // 將「新增長的元素」保存到newElements中。
        newElements[len] = e;
        // 將newElements賦值給」volatile數組「。
        setArray(newElements);
        return true;
    } finally {
        // 釋放「鎖」
        lock.unlock();
    }
}
複製代碼

說明: add(E e)的做用就是將數據e添加到」volatile數組「中。它的實現方式是,新建一個數組,接着將原始的」volatile數組「的數據拷貝到新數組中,而後將新增數據也添加到新數組中:最後,將新數組賦值給」volatile數組「。在add(E e)中有兩點須要關注。 第一,在」添加操做「開始前,獲取獨佔鎖(lock),若此時有須要線程要獲取鎖,則必須等待;在操做完畢後,釋放獨佔鎖(lock),此時其它線程才能獲取鎖。經過獨佔鎖,來防止多線程同時修改數據!lock的定義以下:

transient final ReentrantLock lock = 
new ReentrantLock();  
複製代碼

第二,操做完畢時,會經過setArray()來更新」volatile數組「。並且,前面咱們提過」即對一個volatile變量的讀,老是能看到(任意線程)對這個volatile變量最後的寫入「;這樣,每次添加元素以後,其它線程都能看到新添加的元素。  3. 獲取 以get(int index)爲例,來對「CopyOnWriteArrayList的刪除操做」進行說明。下面是get(int index)的代碼:

51e409b11aa51c150090697429a953ed.gif
public E get(int index) {     return get(getArray(), index); }

private E get(Object[] a, int index) {     return (E) a[index]; }

51e409b11aa51c150090697429a953ed.gif
說明:get(int index)的實現很簡單,就是返回"volatile數組"中的第index個元素。  4. 刪除 以remove(int index)爲例,來對「CopyOnWriteArrayList的刪除操做」進行說明。下面是remove(int index)的代碼:

public E remove(int index) {
    final ReentrantLock lock = this.lock;
    // 獲取「鎖」
    lock.lock();
    try {
        // 獲取原始」volatile數組「中的數據和數據長度。
        Object[] elements = getArray();
        int len = elements.length;
        // 獲取elements數組中的第index個數據。
        E oldValue = get(elements, index);
        int numMoved = len - index - 1;
        // 若是被刪除的是最後一個元素,則直接經過Arrays.copyOf()進行處理,而不須要新建數組。
        // 不然,新建數組,而後將」volatile數組中被刪除元素以外的其它元素「拷貝到新數組中;最後,將新數組賦值給」volatile數組「。
        if (numMoved == 0)
            setArray(Arrays.copyOf(elements, len - 1));
        else {
            Object[] newElements = new Object[len - 1];
            System.arraycopy(elements, 0, newElements, 0, index);
            System.arraycopy(elements, index + 1, newElements, index,
                             numMoved);
            setArray(newElements);
        }
        return oldValue;
    } finally {
        // 釋放「鎖」
        lock.unlock();
    }
}
複製代碼

**說明:**remove(int index)的做用就是將」volatile數組「中第index個元素刪除。它的實現方式是,若是被刪除的是最後一個元素,則直接經過Arrays.copyOf()進行處理,而不須要新建數組。不然,新建數組,而後將"volatile數組中被刪除元素以外的其它元素"拷貝到新數組中;最後,將新數組賦值給"volatile數組"。和add(E e)同樣,remove(int index)也是」在操做以前,獲取獨佔鎖;操做完成以後,釋放獨佔是「;而且"在操做完成時,會經過將數據更新到volatile數組中"。  5. 遍歷 以iterator()爲例,來對CopyOnWriteArrayList的遍歷操做進行說明。 下面是iterator()的代碼:

public Iterator<E> iterator() {
    return new COWIterator<E>(getArray(), 0);
}
複製代碼

說明:iterator()會返回COWIterator對象。COWIterator實現額ListIterator接口,它的源碼以下:

private static class COWIterator&lt;E&gt; implements ListIterator&lt;E&gt; {
    private final Object[] snapshot;
    private int cursor;

    private COWIterator(Object[] elements, int initialCursor) {
        cursor = initialCursor;
        snapshot = elements;
    }

    public boolean hasNext() {
        return cursor &lt; snapshot.length;
    }

    public boolean hasPrevious() {
        return cursor &gt; 0;
    }

    // 獲取下一個元素
    @SuppressWarnings("unchecked")
    public E next() {
        if (! hasNext())
            throw new NoSuchElementException();
        return (E) snapshot[cursor++];
    }

    // 獲取上一個元素
    @SuppressWarnings("unchecked")
    public E previous() {
        if (! hasPrevious())
            throw new NoSuchElementException();
        return (E) snapshot[--cursor];
    }

    public int nextIndex() {
        return cursor;
    }

    public int previousIndex() {
        return cursor-1;
    }

    public void remove() {
        throw new UnsupportedOperationException();
    }

    public void set(E e) {
        throw new UnsupportedOperationException();
    }

    public void add(E e) {
        throw new UnsupportedOperationException();
    }
}
複製代碼

說明:COWIterator不支持修改元素的操做。例如,對於remove(),set(),add()等操做,COWIterator都會拋出異常!另外,須要提到的一點是,CopyOnWriteArrayList返回迭代器不會拋出ConcurrentModificationException異常,即它不是fail-fast機制的!

CopyOnWriteArrayList示例

下面,咱們經過一個例子去對比ArrayList和CopyOnWriteArrayList。

import java.util.*;
import java.util.concurrent.*;

/*
 *   CopyOnWriteArrayList是「線程安全」的動態數組,而ArrayList是非線程安全的。
 *
 *   下面是「多個線程同時操做而且遍歷list」的示例
 *   (01) 當list是CopyOnWriteArrayList對象時,程序能正常運行。
 *   (02) 當list是ArrayList對象時,程序會產生ConcurrentModificationException異常。
 *
 */
public class CopyOnWriteArrayListTest1 {

    // TODO: list是ArrayList對象時,程序會出錯。
    //private static List<String> list = new ArrayList<String>();
    private static List<String> list = new CopyOnWriteArrayList<String>();
    public static void main(String[] args) {
    
        // 同時啓動兩個線程對list進行操做!
        new MyThread("ta").start();
        new MyThread("tb").start();
    }

    private static void printAll() {
        String value = null;
        Iterator iter = list.iterator();
        while(iter.hasNext()) {
            value = (String)iter.next();
            System.out.print(value+", ");
        }
        System.out.println();
    }

    private static class MyThread extends Thread {
        MyThread(String name) {
            super(name);
        }
        @Override
        public void run() {
                int i = 0;
            while (i++ < 6) {
                // 「線程名」 + "-" + "序號"
                String val = Thread.currentThread().getName()+"-"+i;
                list.add(val);
                // 經過「Iterator」遍歷List。
                printAll();
            }
        }
    }
}
複製代碼

其中一次運行結果:

ta-1, tb-1, ta-1, 
tb-1, 
ta-1, ta-1, tb-1, tb-1, tb-2, 
tb-2, ta-1, ta-2, 
tb-1, ta-1, tb-2, tb-1, ta-2, tb-2, tb-3, 
ta-2, ta-1, tb-3, tb-1, ta-3, 
tb-2, ta-1, ta-2, tb-1, tb-3, tb-2, ta-3, ta-2, tb-4, 
tb-3, ta-1, ta-3, tb-1, tb-4, tb-2, ta-4, 
ta-2, ta-1, tb-3, tb-1, ta-3, tb-2, tb-4, ta-2, ta-4, tb-3, tb-5, 
ta-3, ta-1, tb-4, tb-1, ta-4, tb-2, tb-5, ta-2, ta-5, 
tb-3, ta-1, ta-3, tb-1, tb-4, tb-2, ta-4, ta-2, tb-5, tb-3, ta-5, ta-3, tb-6, 
tb-4, ta-4, tb-5, ta-5, tb-6, ta-6,
複製代碼

結果說明:若是將源碼中的list改爲ArrayList對象時,程序會產生ConcurrentModificationException異常。

GitHub: github.com/wangzhiwubi…

關注公衆號,內推,面試,資源下載,關注更多大數據技術~
                   預計更新500+篇文章,已經更新50+篇~ 
複製代碼
相關文章
相關標籤/搜索