微信公众号:Java成长录
感兴趣可以关注下哦,Java知识点,学习路线规划,Java相关电子书,一起学习呀!。
//////////////////////////////////////////////////////////////////////////
一,相似点
1,CopyOnWriteArray和Arraylist和Vector都是实现了List<>接口;
2,底层都是由动态数组支持,并都支持 增 删 改 查操作;
以下是部分源码
CopyOnWriteArray:
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
private transient volatile Object[] array;
}
Arraylist:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
transient Object[] elementData; // non-private to simplify nested class access
}
Vector:
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
protected Object[] elementData;
}
二,差异处
1,非线程安全:ArrayList
线程安全:Vector,CopyOnWriteArray
线程安全的设计主要体现在以下增删改查方面,所以下面将从源码横向比较下三种集合的不同?
CopyOnWriteArray:
- 增删改之前先持有一个ReentrantLock lock = new ReentrantLock(),操作完之后释放;
- 底层是用volatile transient声明的数组 array,可以实现线程间可见性;
- 读写分离,写时复制出一个新的数组,完成插入、修改或者移除操作后将新数组赋值给array;
a,增加元素:
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();
}
}
b,删除元素:
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];
//将旧数组index+1开始复制到新数组index开始,复制元素个数numMoved
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
//结束释放锁
lock.unlock();
}
}
c,修改元素值
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();
}
}
d,查询元素
//直接获取index对应的元素
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
CopyOnWriteArray小结:增删改的时候获取锁,保证并发安全性,array数组被volatile修饰,线程间可见性,读的时候是没有锁的,读操作过程中如果由增删改操作,那么读操作依然能读,不过读到的可能是旧值,但是不会导致获取不到的情况。
ArrayList:
- 插入前会判断数组容量是否足够,不够的话会进行扩容,就是新建一个新的数组,然后将老的数据里面的元素复制到新的数组里面
- 移除元素的时候也涉及到数组中元素的移动,删除指定index位置的元素,然后将index+1至数组最后一个元素往前移动一个格
- modCount表示增加删除元素操作的次数,主要目的是保证读取的时候不允许对数组在进行插入或删除操作,如果modCount不一致,会报异常ConcurrentModificationException
增加元素:
public boolean add(E e) {
//进行数组容量判断,不够就扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
//检查是否会越界
rangeCheckForAdd(index);
//进行数组容量判断,不够就扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
//将index至数据最后一个元素整体往后移动一格,然后插入新的元素
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
删除元素:
public E remove(int index) {
//判断是否越界
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
//若该元素不是最后一个元素的话,将index+1至数组最后一个元素整体向前移动一格
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
修改元素:
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
获取元素:
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
ArrayList小结:ArrayList底层是数组,支持动态扩容,扩容为原来的1.5倍,由于数组的结构导致,ArrayList查询和修改元素速率很快O(1),但是增加,删除元素O(N)比较慢,需要将元素整体移动去腾出位子,或者去除一个位子。
另外,由于线程不安全问题也是一个问题的源点,ArrayList会越界吗?
首先答案是会越界,是数组都会有越界的可能性,但是不是进行插入前都会判断是否越界并且支持动态扩容吗?
单线程是不会在正常情况下越界的,来看下多线程如何越界的吧。
举个例子:
集合默认长度10,第一次扩容位10+10/2 =15,当集合中已经添加了14个元素时,一个线程率先进入add()方法,在执行ensureCapacityInternal(size + 1)时,发现还可以添加一个元素,故数组没有扩容,但随后该线程被阻塞在此处。接着另一线程进入add()方法,执行ensureCapacityInternal(size + 1),由于前一个线程并没有添加元素,故size依然为14,依然不需要扩容,所以该线程就开始添加元素,使得size++,变为15,数组已经满了。而刚刚阻塞在elementData[size++] = e;语句之前的线程开始执行,它要在集合中添加第16个元素,而数组容量只有15个,所以就发生了数组下标越界异常!
Vector:
- 可以在申明一个Vector时候,输入一个capacityIncrement,申明扩容量,不申明默认扩为两倍;
- 增加删除synchronize关键字修适,保证并发安全性,但是效率会降低;
增加元素:
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
public void add(int index, E element) {
insertElementAt(element, index);
}
删除元素:
public boolean remove(Object o) {
return removeElement(o);
}
public synchronized E remove(int index) {
modCount++;
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
int numMoved = elementCount - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--elementCount] = null; // Let gc do its work
return oldValue;
}
修改元素:
public synchronized E set(int index, E element) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
读取元素:
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
Vector小结:可以看到Vector在增删改查方法都用到了synchronized关键字进行了修饰,所以能保证线程安全,但是也会导致效率低下,此集合也是基本属于淘汰的集合啦。
总结:ArrayList,不具有线程安全性,如果想要安全的集合,可以使用其他支持线程安全的集合,或者Collections.synchronizedList生成线程安全集合,也是最常用的List集合;Vector是增删改查方法都加了synchronized关键字来保证同步,但是每个方法执行的时候都要去获得锁,性能就会大大下降,而CopyOnWriteArrayList 只是在增删改上加锁,但是读不加锁,在读方面的性能就好于Vector,CopyOnWriteArrayList支持读多写少的并发情况。