1. 程式人生 > >CopyOnWriteArrayList原始碼解析(1)

CopyOnWriteArrayList原始碼解析(1)

此文已由作者趙計剛授權網易雲社群釋出。

歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗。


注:在看這篇文章之前,如果對ArrayList底層不清楚的話,建議先去看看ArrayList原始碼解析。

http://www.cnblogs.com/java-zhao/p/5102342.html

1、對於CopyOnWriteArrayList需要掌握以下幾點

  • 建立:CopyOnWriteArrayList()

  • 新增元素:即add(E)方法

  • 獲取單個物件:即get(int)方法

  • 刪除物件:即remove(E)方法

  • 遍歷所有物件:即iterator(),在實際中更常用的是增強型的for迴圈去做遍歷

注:CopyOnWriteArrayList是一個執行緒安全,讀操作時無鎖的ArrayList。

 

2、建立

public CopyOnWriteArrayList()

使用方法:

List<String> list = new CopyOnWriteArrayList<String>();

相關原始碼:

    private volatile transient Object[] array;//底層資料結構

    /**
     * 獲取array
     */
    final Object[] getArray() {
        return array;
    }

    /**
     * 設定Object[]
     */
    final void setArray(Object[] a) {
        array = a;
    }

    /**
     * 建立一個CopyOnWriteArrayList
     * 注意:建立了一個0個元素的陣列
     */
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

注意點:

  • 設定一個容量為0的Object[];ArrayList會創造一個容量為10的Object[]

 

3、新增元素

public boolean add(E e)

使用方法:

list.add("hello");

原始碼:

    /**
     * 在陣列末尾新增元素
     * 1)獲取鎖
     * 2)上鎖
     * 3)獲取舊陣列及其長度
     * 4)建立新陣列,容量為舊陣列長度+1,將舊陣列拷貝到新陣列
     * 5)將要增加的元素加入到新陣列的末尾,設定全域性array為新陣列
     */
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;//這裡為什麼不直接用this.lock(即類中已經初始化好的鎖)去上鎖
        lock.lock();//上鎖
        try {
            Object[] elements = getArray();//獲取當前的陣列
            int len = elements.length;//獲取當前陣列元素
            /*
             * Arrays.copyOf(elements, len + 1)的大致執行流程:
             * 1)建立新陣列,容量為len+1,
             * 2)將舊陣列elements拷貝到新陣列,
             * 3)返回新陣列
             */
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;//新陣列的末尾元素設成e
            setArray(newElements);//設定全域性array為新陣列
            return true;
        } finally {
            lock.unlock();//解鎖
        }
    }

注意點:

  • Arrays.copyOf(T[] original, int newLength)該方法在ArrayList中講解過

疑問:

  • 在add(E)方法中,為什麼要重新定義一個ReentrantLock,而不直接使用那個定義的類變數鎖(全域性鎖)

    • 答:事實上,按照他那樣寫,即使是在add、remove、set中存在多個引用,最後也是一個例項this.lock,所以不管你在add、remove、set中怎樣去從新定義一個ReentrantLock,其實add、remove、set中最後使用的都是同一個鎖this.lock,也就是說,同一時刻,add/remove/set只能有一個在執行。這樣講,就是說,下邊這段程式碼完全可以做一個修改。修改前的程式碼:

          public boolean add(E e) {
              final ReentrantLock lock = this.lock;//這裡為什麼不直接用this.lock(即類中已經初始化好的鎖)去上鎖
              lock.lock();//上鎖

      修改後的程式碼:

          public boolean add(E e) {
              //final ReentrantLock lock = this.lock;//這裡為什麼不直接用this.lock(即類中已經初始化好的鎖)去上鎖
              this.lock.lock();//上鎖
  • 根據以上程式碼可知,每增加一個新元素,都要進行一次陣列的複製消耗,那為什麼每次不將陣列的元素設大(比如說像ArrayList那樣,設定為原來的1.5倍+1),這樣就會大大減少因為陣列元素複製所帶來的消耗?

 

4、獲取元素

public E get(int index)

使用方法:

list.get(0)

原始碼:

    /**
     * 根據下標獲取元素
     * 1)獲取陣列array
     * 2)根據索引獲取元素
     */
    public E get(int index) {
        return (E) (getArray()[index]);
    }

注意點:

  • 獲取不需要加鎖

疑問:在《分散式Java應用:基礎與實踐》一書中作者指出:讀操作會發生髒讀,為什麼?

從類屬性部分,我們可以看到array陣列是volatile修飾的,也就是當你對volatile進行寫操作後,會將寫過後的array陣列強制重新整理到主記憶體,在讀操作中,當你讀出陣列(即getArray())時,會強制從主記憶體將array讀到工作記憶體,所以應該不會發生髒讀才對呀!!!

 補:volatile的介紹見《附2 volatile》,連結如下:

http://www.cnblogs.com/java-zhao/p/5125698.html


免費領取驗證碼、內容安全、簡訊傳送、直播點播體驗包及雲伺服器等套餐

更多網易技術、產品、運營經驗分享請點選


相關文章:
【推薦】 10本最熱門科普書免費送!人工智慧數學物理獲獎經典佳作!