网上代理 建网站,网站小程序制作公司,中国网络经纪人,网站被降权了怎么办一、CopyOnWriteArrayList#xff08;一#xff09;
1. 简介
并发包中的并发List只有CopyOnWriteArrayList。
CopyOnWriteArrayList是一个线程安全的ArrayList#xff0c;对其进行的修改操作都是在底层的一个复制的数
组#xff08;快照#xff09;上进行的#xff0…一、CopyOnWriteArrayList一
1. 简介
并发包中的并发List只有CopyOnWriteArrayList。
CopyOnWriteArrayList是一个线程安全的ArrayList对其进行的修改操作都是在底层的一个复制的数
组快照上进行的也就是使用了写时复制策略。 CopyOnWriteArraylist的类图结构如图 在CopyOnWriteArrayList的类图中每个CopyOnWriteArrayList对象里面有一个array数组对象用来存放具体元素ReentrantLock独
占锁对象用来保证同时只有一个线程对array进行修改。 CopyOnWriteArrayList使用写时复制的策略来保证list的一致性而获取 - 修改 - 写入三步操作并不是原子性的所以在增删改的过程
中都使用了独占锁来保证在某个时间只有一个线程能对list数组进行修改。
另外CopyOnWriteArrayList提供了弱一致性的迭代器从而保证在获取迭代器后其他线程对list的修改是不可见的迭代器遍历的数
组是一个快照。
2. 独占锁 独占锁是一种思想 只能有一个线程获取锁以独占的方式持有锁。和悲观锁、互斥锁同义。
Java中用到的独占锁 synchronizedReentrantLock。
3. 弱一致性的迭代器
遍历列表元素可以使用迭代器。在讲解什么是迭代器的弱一致性前先举一个例子来说明如何使用迭代器 public static void main(String[] args) {CopyOnWriteArrayListString list new CopyOnWriteArrayList();list.add(Hello);list.add(World);IteratorString iterator list.iterator();while (iterator.hasNext()) {System.out.println(iterator.next());}}
运行结果 迭代器的hasNext方法用于判断列表中是否还有元素next方法则具体返回元素。
好了下面来看CopyOnWriteArrayList中迭代器的弱一致性是怎么回事
所谓弱一致性是指返回迭代器后其他线程对list的增删改对迭代器是不可见的下面看看这是如何做到的。 在如上代码中当调用iterator方法获取迭代器时实际上会返回一个COWIterator对象COWIterator对象的snapshot变量保存了当
前list的内容cursor是遍历list时数据的下标。 为什么说snapshot是list的快照呢
明明是指针传递的引用啊而不是副本。如果在该线程使用返回的迭代器遍历元素的过程中其他线程没有对list进行增删改那么
snapshot本身就是list的array因为它们是引用关系。但是如果在遍历期间其他线程对该list进行了增删改那么snapshot就是快照了
因为增删改后list里面的数组被新数组替换了这时候老数组被snapshot引用。这也说明获取迭代器后使用该迭代器元素时其他线
程对该list进行的增删改不可见因为它们操作的是两个不同的数组这就是弱一致性。
示例演示多线程下迭代器的弱一致性的效果。
public class Atomic {private static final CopyOnWriteArrayListString arrayList newCopyOnWriteArrayList();public static void main(String[] args) throws InterruptedException {arrayList.add(hello);arrayList.add(alibaba);arrayList.add(welcome);arrayList.add(to);arrayList.add(hangzhou);Thread threadOne new Thread(() - {//修改list中下标为1的元素为babaarrayList.set(1, baba);//删除元素arrayList.remove(2);arrayList.remove(3);});//保证在修改线程启动前获取迭代器IteratorString itr arrayList.iterator();threadOne.start();// 保证threadOne的run方法执行完毕完成对arrayList的修改Thread.sleep(1000);while (itr.hasNext()) System.out.println(itr.next());}
}
运行结果 在如上代码中main函数首先初始化了arrayList然后在启动线程前获取到了arrayList迭代器。
子线程threadOne启动后首先修改了arrayList的第一个元素的值然后删除了arrayList中下标为2和3的元素。
主线程在子线程执行完毕后使用获取的迭代器遍历数组元素从输出结果我们知道在子线程里面进行的操作一个都没有生效这就是迭
代器弱一致性的体现。
需要注意的是获取迭代器的操作必须在子线程操作之前进行。
二、CopyOnWriteArrayList二
1. 简介
在 ArrayList 的类注释上JDK 就提醒了我们如果要把 ArrayList 作为共享变量的话是线程不安全的推荐我们自己加锁或者使用
Collections.synchronizedList 方法其实 JDK 还提供了另外一种线程安全的 List叫做 CopyOnWriteArrayList
2. 原理
很多时候我们的系统应对的都是读多写少的并发场景。CopyOnWriteArrayList容器允许并发读读操作是无锁的性能较高。至于写
操作比如向容器中添加一个元素则首先将当前容器复制一份然后在新副本上执行写操作结束之后再将原容器的引用指向新容器。
线程安全的多线程环境下可以直接使用无需加锁通过锁 数组拷贝 volatile 关键字保证了线程安全每次数组操作都会把数组拷贝一份出来在新数组上进行操作操作成功之后再赋值回去。
从整体架构上来说CopyOnWriteArrayList 数据结构和 ArrayList 是一致的底层是个数组只不过 CopyOnWriteArrayList 在对数组进
行操作的时候基本会分四步走
加锁从原数组中拷贝出新数组在新数组上进行操作并把新数组赋值给数组容器解锁
除了加锁之外CopyOnWriteArrayList 的底层数组还被 volatile 关键字修饰意思是一旦数组被修改其它线程立马能够感知到
代码如下
private transient volatile Object[] array;
整体上来说CopyOnWriteArrayList 就是利用锁 数组拷贝 volatile 关键字保证了 List 的线程安全。
3. 优点
读操作不加锁性能很高因为无需任何同步措施比较适用于读多写少的并发场景。Java的list在遍历时若中途有别的线程对list容
器进行修改则会抛ConcurrentModificationException异常。而CopyOnWriteArrayList由于其读写分离的思想遍历和修改操作分别
作用在不同的list容器所以在使用迭代器进行遍历时候也就不会抛出ConcurrentModificationException异常了。
4. 缺点
一是内存占用问题毕竟每次执行写操作都要将原容器拷贝一份。数据量大时对内存压力较大可能会引起频繁GC
二是无法保证实时性因为CopyOnWrite的写时复制机制所以在进行写操作的时候内存里会同时驻扎两个对象的内存旧的对象和新
写入的对象注意在复制的时候只是复制容器里的引用只是在写的时候会创建新对象添加到新容器里而旧容器的对象还在使用所
以有两份对象内存。
5. 源码分析
添加操作 public boolean add(E e) {//ReentrantLock加锁保证线程安全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();}}
添加的逻辑很简单先将原容器copy一份然后在新副本上执行写操作之后再切换引用。当然此过程是要加锁的。
删除操作 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)//如果要删除的是列表末端数据拷贝前len-1个数据到新副本上再切换引用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();}}
删除操作同理将除要删除元素之外的其他元素拷贝到新副本中然后切换引用将原容器引用指向新副本。同属写操作需要加锁。
我们再来看看读操作CopyOnWriteArrayList的读操作是不用加锁的性能很高。 public E get(int index) {return get(getArray(), index);}
直接读取即可无需加锁 private E get(Object[] a, int index) {return (E) a[index];}
弱一致性的迭代器
所谓弱一致性是指返回迭代器后其他线程对list的增删改查对迭代器是不可见的
// 演示多线程下迭代器的弱一致性结果
public class copylist {private static volatile CopyOnWriteArrayListString arrayList new CopyOnWriteArrayList();public static void main(String[] args) throws InterruptedException {arrayList.add(hello);arrayList.add(alibaba);arrayList.add(welcome);arrayList.add(to);arrayList.add(hangzhou);Thread threadOne new Thread(new Runnable() {Overridepublic void run() {// 修改list中下标为1的元素为aliarrayList.set(1, ali);// 删除元素arrayList.remove(2);arrayList.remove(3);}});// 保证在修改线程启动前获取迭代器IteratorString itr arrayList.iterator();// 启动线程threadOne.start();// 等待子线程执行完毕threadOne.join();while(itr.hasNext()) {System.out.println(itr.next());}}
}
执行程序
hello
alibaba
welcome
to
hangzhouProcess finished with exit code 0
执行程序
hello
alibaba
welcome
to
hangzhouProcess finished with exit code 0
从输出结果我们知道在子线程里面进行的操作一个都没有生效这就是迭代器弱一致性的体现。
需要注意的是获取迭代器的操作必须在子线程操作之前进行。
6. ArrayList转为线程安全的方法
List list Collections.synchronizedList(new ArrayList());