简介
在通过介绍 Vector 时提到了 CopyOnWriteArrayList 类。它也属于Collection 家族下的一个线程安全的一个集合,而且同样基于对象数组实现。所以,目前基于对象数组实现的目前接触到了 Arraylist ,Vector ,和当前介绍的CopyOnWriteArrayList。而且,三者也可以拿来对比一下各自的应用场景。
1.首先,CopyOnWriteArrayList基于对象数组,但是并不意味着增删比较快(重点),其应用场景在于读多写少情况(埋个伏笔)。
2.CopyOnWriteArrayList实现了 RandmoAccess接口。(同之前一样,就是为了实现快速访问,在ArrayList和vector中已经提出过)。
3.CopyOnWriteArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。
4.CopyOnWriteArrayList 实现java.io.Serializable接口,支持序列化。
5.CopyOnWriteArrayList,是线程安全的。注意 这里单独是写入操作是线程安全的。
通过上面的几点(如果你看了前三节总结的知识点),首先可以总结到 Collection 接口下,很多的工具类提供了很多相似的功能,绝大部分实现了,Cloneable接口,java.io.Serializable接口,RandomAccess接口。这里体现出了一点,面向接口的编程,更便于扩展。不同的工具类只需扩展对应的接口,然后接下来实现关于自己的特性即可。这点非常的重要,这也是延伸出来可以学习到更重要的知识。
继承实现体系:
构造函数
共有三个构造函数,分别为:
1.无参数
/**
* 创建一个空的列表
*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
2.以一个 Collection 为参数构造一个新的列表。
/**
* 创建包含指定元素的列表集合,按照集合返回的顺序的迭代器.
*
* @param c the collection of initially held elements
* @throws NullPointerException if the specified collection * is null
*/
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
//这里是判断是否为同一个类型。
//c.getClass()返回包路径,和当前class名称。
if (c.getClass() == CopyOnWriteArrayList.class)
elements = ((CopyOnWriteArrayList<?>)c).getArray();
else {
//转为array,然后调用Arrays.copyOf(),来构造新的list
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);
}
//这里将返回的新的Array复制给全局变量array
setArray(elements);
}
**
* Sets the array.
*/
final void setArray(Object[] a) {
array = a;
}
/** The array, accessed only via getArray/setArray. */
//volatile关键字,代表此array修改之后立刻对其他线程可见。
//但是要注意,在CopyOnWriteArrayList内,对array的操作都是通过setArray方法。
//因为有一个统一设置array的入口,当array改变后,可以直接对其它线程可见。
private transient volatile Object[] array;
3. 以数组作为参数,造一个新的列表。
/**
* 通过一个数组来穿件集合
*
* @param toCopyIn the array (a copy of this array is used as the
* internal array)
* @throws NullPointerException if the specified array is null
*/
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
常用方法
1.add()方法
/**
* 添加一个节点在集合末尾
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
//synchronized说明,修改集合时,只有一个线程可以操作当前集合。
synchronized (lock) {
//获得当前的集合
Object[] elements = getArray();
int len = elements.length;
//复制原来的集合
Object[] newElements = Arrays.copyOf(elements, len + 1);
//设置新的节点内容,在集合末尾
newElements[len] = e;
//将array引用指向到新数组
setArray(newElements);
return true;
}
}
/**
* 添加元素到指定索引
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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)
//numMoved为0说明index为当前链表末尾
newElements = Arrays.copyOf(elements, len + 1);
else {
//创建新的数组,长度为原来素组长度+1
newElements = new Object[len + 1];
//复制,index之前数组元素
System.arraycopy(elements, 0, newElements, 0, index);
//复制index之后元素
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
//设置index处元素
newElements[index] = element;
// 将array引用指向到新数组
setArray(newElements);
}
}
注意:
通过add方法应该意识到,每当有元素添加的操作时。总是先把当前的集合复制一份,然后返回并且添加新的元素到当前集合,然后才调用setArray方法,使新的集合生效。这也就意味着,CopyOnWriteArrayList的增加元素操作极为耗资源。同比,ArrayList他是提前把数组的长度做扩容,然后将新的元素添加到数组的末尾,但是CopyOnWriteArrayList不能这么做。原因在于,在每次的数据改动时,因为为了避免影响读操作,都需要copy一个原有集合的副本,当操作完毕将引用指向新的集合对象。而并不是在原有的对象上做数据的改动处理,因此扩容也就没有意义。
remove()
/**
* 删除集合内指定索引处的元素
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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)
//numMoved为0,删除最后一个节点
setArray(Arrays.copyOf(elements, len - 1));
else {
//创建新的数组对象
Object[] newElements = new Object[len - 1];
//复制index之前的元素
System.arraycopy(elements, 0, newElements, 0, index);
//将index后的元素,从index开始替换。
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
//将array引用指向到新数组
setArray(newElements);
}
//返回删除的元素
return oldValue;
}
}
get()
/**
* 读操作。
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
return elementAt(getArray(), index);
}
注意:唯一你需要注意到的就是这里没有添加关于线程安全的 synchronized 关键字。
总结
1.CopyOnWriteArrayList 数组有改动,都需要耗费过多的资源用来拷贝数组的副本。因此,读多写少的场景最适合应用,这个就要看具体的业务了。
2.CopyOnWriteArrayList 添加或者删除操作,并不能保证达到实时读。因为数据的引用没有指向新的对象之前,读的仍是旧的数据。(setArray方法调用之前读的仍然是旧数据)。
3.这里更好的体现了一个思想时,读写分离。在多线程应用下,达到线程安全。并且保证数据的一致性。
4.多线程情况下,采用对象副本的方式解决了线程并发冲突问题。