文章目录
- ArrayList和CopyOnWriteArrayList
- 1.Se()方法
- 2.add()方法
- 3.add()添加指定位置的元素
- 4.Remove()方法
- 总结
ArrayList和CopyOnWriteArrayList
我们知道多线程并发时ArraysList是线程不安全,而CopyOnWriteArrayList是线程安全,那么CopyOnWriteArrayList是如何保证线程安全的呢?通过以下常用的方法说明:
1.Se()方法
首先两个set()方法的作用是一样的都是替换指定位置的值,但CopyOnWriteArrayList中的set()方法实现ReentrantLock加锁机制,这就是为什么CopyOnWriteArrayList是线程安全。CopyOnWriteArrayList中通过getArray()方法得到原来的数组可理解为oldArray[ ];再通过get(elements, index)方法将原来该下标位置的值取出即为oldValue;然后判断要修改的值是否与原来的值相等,如果相等则将原数组重新放回,如果不相等则进行元素替换通过Arrays.copyOf(elements, len)方法复制新的数组,将指定位置的值进行修改;最后将修改完的数组放回并释放锁
ArrayList的set()方法
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
CopyOnWriteArrayList的set()方法
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();
}
}
2.add()方法
同理,CopyOnWriteArrayList中的add()方法是实现ReentrantLock锁机制也是线程安全。也是通过getArray()得到原来数组;再通过Arrays.copyOf(elements, len + 1)方法复制原来的数组oldArray[ ],但比原数组的长度多1,是因为将要添加的元素采用尾插法存储至数组尾部;最后依旧将新数组放回并释放锁
ArrayList的add()方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
CopyOnWriteArrayList的add()方法
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();
}
}
3.add()添加指定位置的元素
通过getArray()得到原来数组;先判断指定的下标是否越界或不规范,如果不符合则抛出下标越界的异常,如果符合;再判断该元素移动多少个元素,如果发现移动0个元素那么证明要添加的元素应为数组的尾部,那么直接与添加元素的原理相同,如果不等于0那么证明该元素被添加在数组的中间某个位置,则通过System.arraycopy(elements, 0, newElements, 0, index)方法将该下标之前的元素复制出来至新数组、再通过System.arraycopy(elements, index, newElements, index + 1,numMoved)将该下标后半部分元素复制至新数组,再将元素添加至该下标;最后将新数组放回并释放锁
ArrayList的add()方法
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
CopyOnWriteArrayList的add()方法
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();
}
}
4.Remove()方法
通过getArray()得到原来数组;获取到指定下标的元素、先判断删除的元素是否为数组中最后一个元素,如果是数组中最后一个元素则直接复制len-1个元素至新数组再将新数组放回,如果不是最后一个通过 System.arraycopy(elements, 0, newElements, 0, index)方法将删除元素下标之前的元素复制至新数组再通过System.arraycopy(elements, index + 1, newElements, index, numMoved)方法将删除元素下标之后的元素复制至新数组;最后将新数组放回并释放锁
ArrayList的remove()
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - 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;
}
CopyOnWriteArrayList的add()方法
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();
}
}
总结
- 多线程并发时,ArrayList是线程不安全,而CopyOnWriteArrayList是线程安全就是因为CopyOnWriteArrayList实现ReentrantLock锁机制,也正因为锁机制CopyOnWriteArrayList的速度没有ArrayList的速度快。
- ArrayList是会进行扩容,CopyOnWriteArrayList每次都是复制数组无需扩容,但大量的数组复制也是很消耗内存及cpu的性能。
- CopyOnWriteArrayList由于只在写时加锁,读时无锁,导致多线程读数据时数据并一定是最新的数据,导致数据无法做到实时性,也常用于写少读多的场景。
- 还有一些方法都类似与上面的原理,自己可以找找源代码再查看。