JAVA-基础语法-多线程基础-CopyOnWriteArrayList -2
这个时候就要引入CopyOnWrite思想来解决问题了。
他的思想就是,不用加什么读写锁,锁统统给我去掉,有锁就有问题,有锁就有互斥,有锁就可能导致性能低下,你阻塞我的请求,导致我的请求都卡着不能执行。
那么他怎么保证多线程并发的安全性呢?
很简单,顾名思义,利用“CopyOnWrite”的方式,这个英语翻译成中文,大概就是“写数据的时候利用拷贝的副本来执行”。
你在读数据的时候,其实不加锁也没关系,大家左右都是一个读罢了,互相没影响。
问题主要是在写的时候,写的时候你既然不能加锁了,那么就得采用一个策略。
假如说你的ArrayList底层是一个数组来存放你的列表数据,那么这时比如你要修改这个数组里的数据,你就必须先拷贝这个数组的一个副本。
然后你可以在这个数组的副本里写入你要修改的数据,但是在这个过程中实际上你都是在操作一个副本而已。
这样的话,读操作是不是可以同时正常的执行?这个写操作对读操作是没有任何的影响的吧!
大家看下面的图,一起来体会一下这个过程:

关键问题来了,那那个写线程现在把副本数组给修改完了,现在怎么才能让读线程感知到这个变化呢?
关键点来了,划重点!这里要配合上volatile关键字的使用。
笔者之前写过文章,给大家解释过volatile关键字的使用,核心就是让一个变量被写线程给修改之后,立马让其他线程可以读到这个变量引用的最近的值,这就是volatile最核心的作用。
所以一旦写线程搞定了副本数组的修改之后,那么就可以用volatile写的方式,把这个副本数组赋值给volatile修饰的那个数组的引用变量了。
只要一赋值给那个volatile修饰的变量,立马就会对读线程可见,大家都能看到最新的数组了。
下面是JDK里的 CopyOnWriteArrayList 的源码。
大家看看写数据的时候,他是怎么拷贝一个数组副本,然后修改副本,接着通过volatile变量赋值的方式,把修改好的数组副本给更新回去,立马让其他线程可见的。
// 这个数组是核心的,因为用volatile修饰了
// 只要把最新的数组对他赋值,其他线程立马可以看到最新的数组
private transient volatile Object[] array;
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;
// 然后把副本数组赋值给volatile修饰的变量
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
然后大家想,因为是通过副本来进行更新的,万一要是多个线程都要同时更新呢?那搞出来多个副本会不会有问题?
当然不能多个线程同时更新了,这个时候就是看上面源码里,加入了lock锁的机制,也就是同一时间只有一个线程可以更新。
那么更新的时候,会对读操作有任何的影响吗?
绝对不会,因为读操作就是非常简单的对那个数组进行读而已,不涉及任何的锁。而且只要他更新完毕对volatile修饰的变量赋值,那么读线程立马可以看到最新修改后的数组,这是volatile保证的。
private E get(Object[] a, int index) {
// 最简单的对数组进行读取
return (E) a[index];
}
这样就完美解决了我们之前说的读多写少的问题。
如果用读写锁互斥的话,会导致写锁阻塞大量读操作,影响并发性能。
但是如果用了CopyOnWriteArrayList,就是用空间换时间,更新的时候基于副本更新,避免锁,然后最后用volatile变量来赋值保证可见性,更新的时候对读线程没有任何的影响!
- 点赞
- 收藏
- 关注作者
评论(0)