1. 程式人生 > 其它 >從CopyOnWriteArrayList談等效不可變物件在原始碼中的應用

從CopyOnWriteArrayList談等效不可變物件在原始碼中的應用

1 從CopyOnWriteArrayList談等效不可變物件在原始碼中的應用

CopyOnWriteArrayList的原始碼中應用了等效不可變物件。使得集合在遍歷操作的時候,不用加鎖也能保證執行緒安全。

1.1 CopyOnWriteArrayList Source Code

    public class CopyOnWriteArrayList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
            private static final long serialVersionUID = ... ;
            
            final transient ReentrantLock lock = new ReentrantLock();
            
            private transient volatile Object[] array;
            
            final Object[] getArray() {return array;}

            final void setArray(Object[] a) {array = a;}
        }

可以看到CopyOnWriteArrayList原始碼中,維護了一個array物件陣列,用於儲存集合中每一個元素,並且這個array陣列,只能夠通過get和set方法來訪問。

    // 遍歷CopyOnWriteArrayList方法
    public Iterator<E> iterator(){
        return new COWIterator<E>(getArray(), 0);
    }
    // 新增一個元素的方法
    public boolean add(E e){
        final ReentrantLock lock = this.lock;
        lock.lock();
        try{
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elementes, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

增加元素的步驟:

  1. elements = 當前陣列
  2. len = 當前陣列長度
  3. 新建一個 newElements,把elements複製進去,並且長度增加1
  4. 把增加的元素放在newElements最後
  5. 調取setArray方法,將newElements陣列更新舊的陣列。
  6. 返回true,解鎖。

1.2 分析關鍵點

  • array被建立後,不會被修改。包括長度和內容。
  • 增刪時,先整體複製array到newElements中,在新的陣列中做好操作,直接整體替換舊陣列。
  • 在操作新陣列時,舊陣列內的數值至始至終不改變。

例項Array本質上就是一個數組,陣列內的元素是物件,每個物件的內部狀態可以發生替換,因此這並非是嚴格的不可變物件,所以稱之為等效不可變物件

1.3 繼續說明

下面演示上述所謂的非嚴格的不可變物件的情況

    public static void main(String[] args){
        List<Message> list = new CopyOnWriteArrayList<>();
        Message message = new Message();
        message.setMsg("aaa");
        list.add(message);
        message.setMsg("bbb");
    }

照理來說,放在CopeOnWriteArrayList中的東西應該不能動,除非整個替換。但是我們可以通過呼叫存在陣列中的物件的set方法,修改了物件數值。

這就是不嚴格的不可變物件。 稱之為:等效不可變物件

1.4 精髓

這個陣列是寫時複製,也就是當要更新陣列內容時,先複製一個副本,在副本上做修改,然後用副本替換原本。

寫時複製的意義在於,當讀多寫少的場景時,通過這個機制,大量的讀請求在無需加鎖犧牲效能的情況下,保證多執行緒的併發讀寫安全。

這個陣列的弱點在於:弱一致性

因為任何執行緒從原理上,只不過是獲得了一個數組備份的副本而已,當原本陣列發生改變時,這種改變,並不會體現在副本上。

1.5 實際應用 in MySQL Driver

在mysql driver中,有一個registerDriver方法,該方法用來儲存不同的資料庫驅動的。

原始碼省略。

無論有多少個數據庫模型,資料庫的驅動程式一般都是在程式啟動的時候載入的。也就是說,registerDriver方法,一般來說都是在程式啟動的時候進行呼叫的,在程式執行的過程中,一般不會呼叫這個方法,所以非常適合這個List