小渣渣,如有什么问题欢迎指正。
JDK版本1.8
get()方法
CopyOnWriteArrayList中的get方法
private transient volatile Object[] array;
public E get(int index) {
return get(getArray(), index);
}
final Object[] getArray() {
return array;
}
private E get(Object[] a, int index) {
return (E) a[index];
}
get(index)方法调用内部的getArray()获得数组,再通过get(array,index)来获取元素
ArrayList中的get方法
transient Object[] elementData;
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
rangeCheck()方法是用来检查index是否超出数组大小,不管它。get(index)方法调用了elementData(index)方法直接返回了数组元素。
区别
很明显两者区别在于CopyOnWriteArrayList中多了一步getArray(),可以看到getArray()方法的修饰符:final。
final字段修饰了getArray()方法,final修饰符在修饰变量时在其后增加了一层sfence,这一层sfence禁用了sfence前后指令的重排序,使得调用该方法之前对数组的修改都是可见的,也就保证了并发的数据一致性。
而CopyOnWriteArrayList中的array是一个volatile变量,读写操作都具有Happends-Before关系。
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();
}
}
可以看到set方法定义了一个可重入锁,是一个独占锁,并在一进入set方法就进行加锁,然后在一个副本newElements上修改元素,然后替换掉原来的数组。
这个锁保证了每次只有一个线程进行写操作,但是其它线程会读取数组数据,而出现读写竞争的情况。其中array是volatile类型变量,而先set方法现在副本上修改,然后修改数组的引用,使得这一瞬间的修改对于其它线程都是可见的,保证了数据一致性,也就是“写时复制”。
对于其它线程来说,拿到的副本可能是失效的,但是一定是集合某一瞬间的快照,一定程度上满足不变形,满足弱一致性。