CopyOnWriteArrayList
CopyOnWriteArrayList诞生记
适用场景
CopyOnWriteArrayList:就是不能一起写,可以一边读一边写,也可以一边写一边读。从这个方面来说,比读写锁强,读写锁是可以多读一写。
代码演示
package copyonwrite;
import java.util.ArrayList;
import java.util.Iterator;
/**
* 描述: 演示CopyOnWriteArrayList可以在迭代过程中修改数组内容,但是ArrayList不行,对比
*/
public class CopyOnWriteArrayListDemo1 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println("list is" + list);
String next = iterator.next();
System.out.println(next);
if (next.equals("2")){
list.remove("5");
}
if (next.equals("3")){
list.add("3 found");
}
}
}
}
用ArrayList迭代过程中,修改会报错;
当改为CopyOnWriteArrayList,其他都不变的情况下
package copyonwrite;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 描述: 演示CopyOnWriteArrayList可以在迭代过程中(读)修改数组内容,但是ArrayList不行,对比
*/
public class CopyOnWriteArrayListDemo1 {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println("list is" + list);
String next = iterator.next();
System.out.println(next);
if (next.equals("2")){
list.remove("5");
}
if (next.equals("3")){
list.add("3 found");
}
}
}
}
结果如下:
这时候,可以看到CopyOnWriteArrayList的特性,就是你管你的修改,我管我的迭代。我还是拿着我之前没改过的内容来进行迭代。
CopyOnWrite:
在计算机中,当要修改内存时,有一种机制。就是,我先复制一份内存,复制原内存中的东西。然后,再进行修改。然后,指向原来那个内存的指针,指向新的内存。这样,原来那块内存的指针就可以被回收了。
CopyOnWrite:就是这个这个机制,它在写的时候,去Copy一份出来,然后,在新的内存经常操作,全部搞定以后,再把容易的应用指向新的内存中。这时候的好处,就是在读的时候,不会有干扰。
不可变原理指的是在CopyOnWriteArrayList中,每次该的时候,就会创建一个新的副本,对于旧的副本来说就是不可变的,所以,不管有多少线程并发去读它,它都是安全的。
在迭代的过程中,该数据CopyOnWriteArrayList不会报错,而ArrayList则会报错。
ArrayList报错原因分析:
在迭代过程中,会先检测 checkForComodification();
下面进入到checkForComodification()进行查看
这里面有modCount 和 expectedModCount这两个参数,其中,modCount代表的是改变的次数,expectedModCount代表的是希望改变的次数。
在我们新建迭代器的时候,我们会把当前的modCount给存下来。
所以,在你用迭代器进行迭代的时候,如果对modCount 进行更改,就会抛出ConcurrentModificationException异常了。
但是,CopyOnWriteArrayList不会抛出异常。但是,他有一个问题,它的数据可能是过期的。
package copyonwrite;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 描述: 对比两个迭代器
*/
public class CopyOnWriteArrayListDemo2 {
public static void main(String[] args) {
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>( new Integer[]{1,2,3});
System.out.println(list);
// 它所能拿到的数据取决于它的诞生时间,而不是它的迭代时间
Iterator<Integer> itr1 = list.iterator();
list.add(4);
System.out.println(list);
Iterator<Integer> itr2 = list.iterator();
itr1.forEachRemaining(System.out::println);
itr2.forEachRemaining(System.out::println);
}
}
结果如下:
结论:它所能拿到的数据取决于它的诞生时间,而不是它的迭代时间。
首先,要去查看一下CopyOnWriteArrayList的数据结构,主要看的是锁。
发现锁为ReentrantLock
然后去看add方法
其源码如下:先进行加锁,然后复制一份新的数组,然后把add这个加进去。然后,再让原来的指针去指向它。再进行解锁。所以,从时间复杂度来看,CopyOnWrite系列的集合,都不适合进行添加,比较时间复杂度太高了。
下面在进行查看get方法
可以发现他是没有锁的。很显然,这就效率很快。但是,可能导致更新不及时的问题。
可能你添加元素时,它还没有让指针指向新的数组,但是,你调用get去查了。可能查不到。但是,他速度还是很快的。应该发生的概率不大。