JUC学习——day03
目录:
文章目录
List集合类(安全和不安全)
不安全:ArrayList (为了替代Vector,出现在Jdk1.2)
安全 :Vector (出现在Jdk1.0)、 Collections.synchronizedList() 、 CopyOnWriteList
一、Vector和SynchronizedList
1.1回顾线程安全的Vector和SynchronizedList
-
ArrayList是用于替代Vector的,Vector是线程安全的容器。因为它几乎在每个方法声明处都加了synchronized关键字来使容器安全。
-
如果使用Collections.synchronizedList(new ArrayList())来使ArrayList变成是线程安全的话,也是几乎都是每个方法都加上synchronized关键字的,只不过它不是加在方法的声明处,而是方法的内部。
二、CopyOnWriteArrayList
为了将读取的性能发挥到极致,提供了CopyOnWriteArrayList类,该类在使用过程中,读读之间不互斥并且更厉害的是读写也不互斥。下面,我们来看看它如何做到的:
public boolean add(E e) {
//获取重入锁
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
//得到旧数组并获取旧数组的长度
Object[] elements = getArray();
int len = elements.length;
//复制旧数组的元素到新的数组中并且大小在原基础上加1
Object[] newElements = Arrays.copyOf(elements, len + 1);
//把值插入到新数组中
newElements[len] = e;
//使用新数组替换老数组
setArray(newElements);
return true;
} finally {
//释放锁
lock.unlock();
}
}
从源码中,我们可以看出add操作中使用了重入锁,但是此锁只针对写-写操作。为什么读写之间不用互斥,关键就在于添加值的操作并不是直接在原有数组中完成,而是使用原有数组复制一个新的数组,然后将值插入到新的数组中,最后使用新数组替换旧数组,这样插入就完成了。大家可以发现,使用这种方式,在add的过程中旧数组没有得到修改,因此写入操作不影响读取操,另外,数组定义private transient volatile Object[] array,其中采用volatile修饰,保证内存可见性,读取线程可以马上知道这个修改。下面我们来看看读取的操作:
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
final Object[] getArray() {
return array;
}
读取操作完全没有使用任何的同步控制或者是加锁,这是因为array数组内部结构不会发生任何改变,只会被另外一个array所替换,因此读取是线程安全的。
2.1 剖析为什么遍历时不用调用者显式加锁
常用的方法实现我们已经基本了解了,但还是不知道为啥能够在容器遍历的时候对其进行修改而不抛出异常。所以,来看一下他的迭代器吧:
到这里,我们应该就可以想明白了!CopyOnWriteArrayList在使用迭代器遍历的时候,操作的都是原数组!
2.2 CopyOnWriteArrayList缺点
内存占用:如果CopyOnWriteArrayList经常要增删改里面的数据,经常要执行add()、set()、remove()的话,那是比较耗费内存的。
因为我们知道每次add()、set()、remove()这些增删改操作都要复制一个数组出来。
数据一致性:CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。
从上面的例子也可以看出来,比如线程A在迭代CopyOnWriteArrayList容器的数据。线程B在线程A迭代的间隙中将CopyOnWriteArrayList部分的数据修改了(已经调用setArray()了)。但是线程A迭代出来的是原有的数据。
三、COW(写时复制)
3.1 什么是 COW?
Copy-On-Write简称COW。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。
1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。