從CopyOnWriteArrayList談等效不可變物件在原始碼中的應用
阿新 • • 發佈:2021-06-10
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(); } }
增加元素的步驟:
- elements = 當前陣列
- len = 當前陣列長度
- 新建一個 newElements,把elements複製進去,並且長度增加1
- 把增加的元素放在newElements最後
- 調取setArray方法,將newElements陣列更新舊的陣列。
- 返回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