◆
CopyOnWriteArrayList简介
◆
CopyOnWriteArrayList和ArrayList一样是一个动态数组。而与ArrayList不同的是,它是线程安全的。
建议先阅读 ArrayList源码分析 ,再回来看此文会Soeasy哦!
CopyOnWriteArrayList实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。
List提供了添加、删除、修改、遍历等功能。
RandmoAccess提供了随机访问功能
Cloneable提供了可以被克隆的功能
Serializable提供了序列化的功能
◆
CopyOnWriteArrayList的属性
◆
/**实现线程安全的锁对象*/
final transient ReentrantLock lock = new ReentrantLock();
/** 元素缓冲区,volatile保证多线程下始终读取到最新的数据*/
private transient volatile Object[] array;
上方的lock对象就是CopyOnWriteArrayList实现线程安全的秘诀。
对于Java中的锁有疑问的同学可以参考此文章: Java中的锁
◆
CopyOnWriteArrayList的构造方法
◆
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
public CopyOnWriteArrayList(Collection extends E> c) {
Object[] elements;
if (c.getClass() == java.util.concurrent.CopyOnWriteArrayList.class)
elements = ((CopyOnWriteArrayList>)c).getArray();
else {
elements = c.toArray();
// c.toArray might (incorrectly) not return Object[] (see 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 void setArray(Object[] a) {
array = a;
}
三个构造方法最后都是调用的setArray方法完成的初始化。
◆
CopyOnWriteArrayList的方法
◆
接下来我们就以CopyOnWriteArrayList的几个比较经典的方法来看一下它是如何设计的。
首先是添加方法:
/**
* 添加
*/
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();
}
}
/**
* 指定索引添加
*/
public void add(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+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);
} finally {
lock.unlock();
}
}
final Object[] getArray() {
return array;
}
final void setArray(Object[] a) {
array = a;
}
可以看的,进行添加之前首先是要先获得锁对象才继续进行的操作(包括修改和删除),只有这样才能保证线程安全。
接着看添加的逻辑
新建一个数组,接着将通过getArray()方法获取到的原始的数组拷贝到新数组中,然后将新增数据也添加到新数组中;最后将新数组赋值给原先的数组。
接下来看删除操作:
/**
* 删除
*/
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(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;
} finally {
lock.unlock();
}
}
同添加方法相同的逻辑,先获取锁,然后通过copy数组方式进行删除操作。
接下来修改方法,修改的时候也使用到了查询方法:
/**
* 替换指定索引的元素
*/
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
E oldValue = get(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;
} finally {
lock.unlock();
}
}
/**
* get方法
*/
public E get(int index) {
return get(getArray(), index);
}
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}
第一步同样是加锁,接着会有一个判断,看要修改的索引位置的元素是否相同,不相同则继续通过copy数组的方式进行替换,最后使用setArray()方法更新。
看了CopyOnWriteArrayList的增删改查方法你就应该明白一件事,这哥们除了查询、增删改都很慢呀。
与ArrayList相比,CopyOnWriteArrayList最值得我们注意的地方就是:
增删改操作必须获取锁之后才能进行,操作完毕释放锁其他操作才可以继续执行
get元素是使用volatile修饰的,可以保证多线程之间的数据更新同步
不得不看
1.SpringCloud系列博客汇总
2.Java多线程面试必备基础知识汇总
万水千山总是情,点个 “在看” 行不行!!!