fail-fast 機(jī)制是集合世界中比較常見的錯(cuò)誤檢測(cè)機(jī)制挖滤,通常出現(xiàn)在遍歷集合元素的過程中。它是一種對(duì)集合遍歷操作時(shí)的錯(cuò)誤檢測(cè)機(jī)制,在遍歷中途出現(xiàn)意外的修改時(shí)愈案,通過 unchecked 異常暴力地反饋出來。這種機(jī)制經(jīng)常出現(xiàn)在多線程環(huán)境下鹅搪,當(dāng)前線程會(huì)維護(hù)一個(gè)計(jì)數(shù)比較器站绪, 即 expectedModCount, 記錄已經(jīng)修改的次數(shù)丽柿。在進(jìn)入遍歷前恢准,會(huì)把實(shí)時(shí)修改次數(shù) modCount 賦值給 expectedModCount,如果這兩個(gè)數(shù)據(jù)不相等甫题,則拋出異常顷歌。 java.util 下的所有集合類都是 fail-fast,而 concurrent 包中的集合類都是 fail-safe幔睬。
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("one");
names.add("two");
names.add("three");
for (String name : names) {
if ("one".equals(name)) {
names.remove(name);
}
}
System.out.println(names);
}
運(yùn)行程序眯漩,出現(xiàn)異常:
Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$
Itr.checkForComodification(ArrayList.java:907)
at java.util.ArrayList$
Itr.next(ArrayList.java:857) at
這是因?yàn)?ArrayList 內(nèi)部的 Iterator 在執(zhí)行 next 方法時(shí),首先檢查 modCount麻顶。而 ArrayList 的 remove 方法會(huì)使 modCount 加 1赦抖,這就導(dǎo)致 Iterator 內(nèi)部的 expectedModCount 與 ArrayList 中的 modCount 不一致,所以拋出異常辅肾。
那么移除元素的正確姿勢(shì)是什么呢队萤?就是使用 Iterator 的 remove 方法,它會(huì)修改游標(biāo)位置和 expectedModCount矫钓。如果是多線程并發(fā)要尔,還需要再 Iterator 遍歷時(shí)加鎖舍杜。
while (iterator.hasNext()){
synchronized (lock) {
String name = iterator.next();
if ("one".equals(name)) {
iterator.remove();
}
}
}
COW(奶牛家族),即 Copy-On-Write赵辕,它是并發(fā)的一種新思路既绩,實(shí)行讀寫分離,如果是寫操作还惠,則復(fù)制一個(gè)新集合饲握,在新集合內(nèi)添加或刪除元素。待一切修改完成之后蚕键,再將原集合的引用指向新的集合救欧。這樣做的好處是可以高并發(fā)地對(duì) COW 進(jìn)行讀和遍歷操作,而不需要加鎖锣光,因?yàn)楫?dāng)前集合不會(huì)添加任何元素笆怠。使用 COW 時(shí)應(yīng)注意兩點(diǎn):第一,盡量設(shè)置合理的容量初始值誊爹,它擴(kuò)容的代價(jià)比較大骑疆;第二,使用批量添加或刪除方法替废,如 addAll 或 removeAll操作箍铭,在高并發(fā)請(qǐng)求下,可以攢一下要添加或者刪除的元素椎镣,避免增加一個(gè)元素復(fù)制整個(gè)集合诈火。適用于讀多寫少的場(chǎng)景。
看看 COW 添加元素的方法状答,內(nèi)部實(shí)現(xiàn)一目了然冷守,線程安全,寫時(shí)復(fù)制惊科。
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
COW 是 fail-safe 機(jī)制的拍摇,在并發(fā)包的集合申都是由這種機(jī)制實(shí)現(xiàn)的,fail-safe 是在安全的副本(或者沒有修改操作的正本)上進(jìn)行遍歷馆截,集合修改與副本的遍歷是沒有任何關(guān)系的充活,但是缺點(diǎn)也很明顯,就是讀取不到最新的數(shù)據(jù)蜡娶。這也是 CAP 理論中 C(Consistency)與 A (Availability) 的矛盾混卵,即一致性與可用性的矛盾。
本文的一些內(nèi)容來自:《碼出高效:Java開發(fā)手冊(cè)》窖张,這時(shí)一本非常好的 Java 進(jìn)階書幕随,強(qiáng)烈推薦多讀幾遍!