ArrayList是线程不安全的,在早期可以用Vector代替,或者利用Collections工具类,通过包装实现线程安全,但是这两种方方式效率低下。来看一下Vector源码
可以看出它实现线程安全的方式就是在方法上加锁,我们知道synchronized关键字是独占的效率低下,再看下利用Collections工具类,通过包装:
Collections.synchronizedList(new ArrayList<String>());
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
在底层是通过同步代码块实现的线程安全,且锁对象是一个,所以效率低下,所以JDK1.5之后出现了CopyOnWriteArrayList,既能保证线程安全,效率又没有大大减低。类似的还有CopyOnWriteArraySet。
CopyOnWriteArrayList原理是对其修改在在副本的基础上,所以可以一边迭代一边修改,这和ArrayList明显的区别,特别适合迭代数据比较多的情况,CopyOnWriteArrayList是对读写锁的升级,读操作是不需要加锁的,写操作也不会阻塞读操作,只有写写操作才需要同步,而读写锁除了读读共享其他操作都是互斥的。
CopyOnWriteArrayList适合读多写少的情况,而且读取可以尽量的快,即使写慢一点也可以。
重要方法分析重点是修改操作
//底层使用一个数组表示且被volatile修饰保证了可见性
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
/**
*
* 从 CopyOnWriteArraySet class.获得底层数组
*/
final Object[] getArray() {
return array;
}
/**
* 设置数组
*/
final void setArray(Object[] a) {
array = a;
}
/**
* 替换指定位置元素的值
*
* @throws越界异常
*/
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();
}
}
/**
* 最加元素到数组尾巴上.
*
*
* @return {@code true} (as specified by {@link Collection#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();
}
}
总之写操作时需要加锁的,所以写写互斥,而且写是在副本的基础上修改,所以根本不需要阻塞读操作,所以可以迭代的同时修改,看一下读取操纵源码:
/**
* {@inheritDoc}
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
return get(getArray(), index);
}
// Positional Access Operations
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}
我们可以看到读取就是数组的简单读取元素操作,没加任何同步操作。
正因为修改操作是在副本的基础上,所以会浪费内存,而且只能保证最终数据的一致性,不能保证实时一致性,如果要保证实时一致性就别用CopyOnWrite
/**
* 描述: 对比两个迭代器
*/
public class CopyOnWriteArrayListDemo2 {
public static void main(String[] args) throws InterruptedException {
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>(new Integer[]{1, 2, 3});
System.out.println(list);
Iterator<Integer> itr1 = list.iterator();
list.remove(2);
Thread.sleep(1000);
System.out.println(list);
Iterator<Integer> itr2 = list.iterator();
itr1.forEachRemaining(System.out::println);
itr2.forEachRemaining(System.out::println);
}
}
数据确定是迭代器产生时,而不是迭代器产生之后。