CopyOnWriteArrayList
读写锁,线程安全的ArrayList。它具有以下特性:
1、它最适合于读远多写操作,需要在遍历期间防止线程间的冲突。
2、它是线程安全的。
3、因为读写锁的一些操作需要复制整个基础数组,因此对于add()、remove()、set()等操作开销很大,因此应避免此类操作。
4、使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。
源码
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
/**
* The lock protecting all mutators. (We have a mild preference
* for builtin monitors over ReentrantLock when either will do.)
*/
final transient Object lock = new Object();
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
可以看出:
1、CopyOnWriteArrayList实现了List接口,因此它是一个队列
。
2、CopyOnWriteArrayList包含了成员lock。每一个CopyOnWriteArrayList都和一个互斥锁lock
绑定,通过lock,实现了对CopyOnWriteArrayList的互斥访问。
3、CopyOnWriteArrayList包含了array[] 数组,这说明CopyOnWriteArrayList和List<>一样,是通过数组
实现的。
volatile Object[] array 和 lock
CopyOnWriteArrayList通过内部的“volatile数组
”(array)来保持数据。当增加修改删除数据时,都会新建一个数组
,并将更新后的数据拷贝到新建的数组中,最后再将该数组赋值给“volatile数组
”。由于它在增加修改删除数据时,都会新建数组,这部分开销很大,所以涉及到这些操作时,CopyOnWriteArrayList的效率
很低。但进行遍历查找的话,效率
比较高。因此适用于读多于写
的情况。
CopyOnWriteArrayList的“线程安全”是通过volatile和互斥锁
来实现的。首先“volatile数组”保存数据可以保证,一个线程操作时总能看到其它线程对该volatile变量最后的写入,读取到的数据总是最新的
。然后CopyOnWriteArrayList通过互斥锁来保护数据。在增加修改删除数据时,会先“获取互斥锁”
,修改完毕之后,先将数据更新到“volatile数组”中,然后再“释放互斥锁”
。从而达到写时的安全。
常见函数的源码分析
构造函数
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
if (c.getClass() == CopyOnWriteArrayList.class)
elements = ((CopyOnWriteArrayList<?>)c).getArray();
else {
elements = c.toArray();
// defend against c.toArray (incorrectly) not returning Object[]
// (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
setArray(elements);
}
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
final Object[] getArray() {
return array;
}
final void setArray(Object[] a) {
array = a;
}
构造函数没什么说的,有参的都是给array数组赋值。
添加 add(E e)
public boolean add(E e) {
synchronized (lock) {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
}
}
public void add(int index, E element) {
synchronized (lock) {
Object[] elements = getArray();
int len = elements.length;
if (index > len || index < 0)
throw new IndexOutOfBoundsException(outOfBounds(index, len));
Object[] newElements;
int numMoved = len - index;
if (numMoved == 0)
newElements = Arrays.copyOf(elements, len + 1);
else {
newElements = new Object[len + 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
newElements[index] = element;
setArray(newElements);
}
}
说明:
1、add(E e)操作是通过synchronized代码块来实现锁,保证每次只有一个线程操作。
2、add(E e)的流程:首先新建一个数组,然后将原volatile数组的数据拷贝到新数组中,然后E e添加到新数组中,最后,将新数组赋值给volatile数组。
注意一下:
本文安装的是java1.10,而之前看的是1.7版本中,使用的是ReentrantLock锁对象,原因,我觉得是后面版本对于synchronized的优化使得synchronized更方便快捷。
1.10中synchronized块中的lock对象,
final transient Object lock = new Object();
获取 get(int index)
public E get(int index) {
return elementAt(getArray(), index);
}
static <E> E elementAt(Object[] a, int index) {
return (E) a[index];
}
说明:
get比较简单,就返回对应index位置的数据就好
更新 set(int index, E element)
public E set(int index, E element) {
synchronized (lock) {
Object[] elements = getArray();
E oldValue = elementAt(elements, index);
if (oldValue != element) {
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len);
newElements[index] = element;
setArray(newElements);
} else {
// Not quite a no-op; ensures volatile write semantics
setArray(elements);
}
return oldValue;
}
}
说明:
1、set的操作流程:首先就是拿到原来的数组,查看一下原来index位置上的值,判断若是和原来的数据一样,就直接不用copy直接setArray返回修改原来的值就好,若是真的修改为不同的值,会先copy一个原来的数组,然后把对应位置的值设置为新值,其实就是数组操作,然后setArray。
2、注意这里设置值也加了互斥锁,并且也copy了数组,为什么呢?因为叫CopyOnWriteArrayList啊!
删除 remove(int index)
public E remove(int index) {
synchronized (lock) {
Object[] elements = getArray();
int len = elements.length;
E oldValue = elementAt(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
}
}
说明:
1、remove(int index)的的操作流程,首先判断如果被删除的是最后一个元素,则直接通过Arrays.copyOf()进行提取之前的数组,且不需要新建数组。若不是删除最后一个元素,则会新建数组,然后分段将删除位置之前和之后的数据copy到新数组,然后将新数组赋值给”volatile数组“。
2、remove(int index)也要获取互斥锁。
iterator() 遍历
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
iterator()会返回COWIterator对象
举个例子
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @author fitz.bai
*/
public class MyThread18 {
private static List<String> list = new CopyOnWriteArrayList<>();
public static void main(String[] args) {
new MyThread("a").start();
new MyThread("b").start();
}
static class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
int i = 0;
while (i++ < 6) {
String val = Thread.currentThread().getName() + "-" + i;
list.add(val);
printAll();
}
}
private void printAll() {
String value = null;
Iterator iter = list.iterator();
while (iter.hasNext()) {
value = (String) iter.next();
System.out.print(value + ", ");
}
System.out.println();
}
}
}
// 运行结果
a-1, a-1,
b-1,
a-1, a-1, b-1, b-1, a-2,
a-2, a-1, b-2,
b-1, a-1, a-2, b-1, b-2, a-2, a-3,
b-2, a-1, a-3, b-1, b-3,
a-2, a-1, b-2, b-1, a-3, a-2, b-3, b-2, a-4,
a-3, a-1, b-3, b-1, a-4, a-2, b-4,
b-2, a-3, b-3, a-4, a-1, b-4, a-5,
b-1, a-1, a-2, b-1, b-2, a-3, a-2, b-3, b-2, a-4, a-3, b-4, b-3, a-5, a-4, b-5,
b-4, a-1, a-5, b-1, b-5, a-6,
a-2, b-2, a-3, b-3, a-4, b-4, a-5, b-5, a-6, b-6,
若是AraayLIst则会报错java.util.ConcurrentModificationException