上一篇:fail-fast机制详细说明
为了避免出发fail-fast机制,导致异常,我们可以使用Java中提供的一些采用了fail-safe机制的集合类。
这样的集合容器在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,再copy在街上进行遍历。
java.util.concurrent包下的容器都是fail-safe的,可以在多线程下并发使用,并发修改。同时也可以在foreach中进行add/remove。
拿CopyOnWriteArrayList这个fail-safe的集合类来简单分析一下。
public static void main(String args[]) {
List<String> userNames = new CopyOnWriteArrayList<String>(){{
add("yang");
add("yang");
add("yangChuang");
add("Y");
}};
userNames.iterator();
for(String userName : userNames) {
if(userName.equals("yang")) {
userNames.remove(userName);
}
}
System.out.println(userNames);
}
以上代码,使用CopyOnWriteArrayList 代替了ArrayList,就不会发生异常。
fail-safe集合的所有对集合的修改都是先拷贝一份副本,然后在副本集合上进行的,并不是直接对原集合进行修改。并且,这些修改方法,如add/remove都是通过加锁来控制并发的。
所以,CopyOnWriteArrayList 中的迭代器在迭代的过程中不需要做fail-fast的并发检测。(因为fail-fast的主要目的就是识别并发,然后通过异常的方式通知给用户)
但是,基于拷贝内容的优点是避免了ConcurrentModificationException,但是同样的,迭代器并不能访问到修改以后的内容。代码如下:
public static void main(String args[]) {
List<String> userNames = new CopyOnWriteArrayList<String>(){{
add("yang");
add("yang");
add("yangChuang");
add("Y");
}};
Iterator it = new userNames.iterator();
for(String userName : userNames) {
if(userName.equals("yang")) {
userNames.remove(userName);
}
}
System.out.println(userNames);
while(it.hasNext()) {
System.out.println(it.next());
}
}
我们得到CopyOnWriteArrayList的Iterator之后,通过for循环直接删除原数组中的值,最后在结尾处输出Iterator,结果发现内容如下:
迭代器遍历的是开始遍历那一刻拿到的集合Copy,在遍历期间原集合发生的修改迭代器是不知道的。
在了解了CopyOnWriteArrayList之后,不知道你们会不会和我一样,有一个疑问:他的add/remove等方法都加锁了,还要拷贝一份再修改干嘛呢?多此一举?同样的是线程安全的集合,这玩意和Vector有什么样的区别呢?
Copy-On-Write简称COW,是一种由于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容拷贝出去形成一个新的内容然后再改,这种策略就叫做 延时懒惰策略。
CopyOnWrite容器即 写时复制的容器。用人话讲就是:当我们往容器添加元素的时间,不直接往容器中添加,而是先将当前容器进行Copy,复制出一个新的容器,然后在新的容器里面添加元素,添加完元素之后,再将原容器的引用指向新容器。
CopyOnWriteArrayList中的add/remove等方法是需要加锁的,目的是为了避免Copy出来N个副本出来,导致并发。
但是,CopyOnWriteArrayList中的读方法是没有加锁的。
public E get( int index) {
return get(getArray(),index);
}
这样做的好处是我们可以对CopyOnWrite容器进行并发的读,当然了,这里读到的数据可能不是最新的。因为写的话我们上面说了用到的是延时懒惰策略,而延时懒惰策略是通过延时更新的策略来实现数据的最终一致性,并非强一致性。
所以CopyOnWrite容器是一种读写分离的思想,读和写不同的容器。
而在Vector读写的时候使用同一个容器,读写互斥,同时只能做一件事。