JAVA-基础语法-多线程基础-CopyOnWriteArrayList -2

举报
Photon 发表于 2020/12/21 12:48:27 2020/12/21
【摘要】 JAVA-基础语法-多线程基础-CopyOnWriteArrayList -2
2、引入 CopyOnWrite 思想解决问题

这个时候就要引入CopyOnWrite思想来解决问题了。

他的思想就是,不用加什么读写锁,锁统统给我去掉,有锁就有问题,有锁就有互斥,有锁就可能导致性能低下,你阻塞我的请求,导致我的请求都卡着不能执行。

那么他怎么保证多线程并发的安全性呢?

很简单,顾名思义,利用“CopyOnWrite”的方式,这个英语翻译成中文,大概就是“写数据的时候利用拷贝的副本来执行”。

你在读数据的时候,其实不加锁也没关系,大家左右都是一个读罢了,互相没影响。

问题主要是在写的时候,写的时候你既然不能加锁了,那么就得采用一个策略。

假如说你的ArrayList底层是一个数组来存放你的列表数据,那么这时比如你要修改这个数组里的数据,你就必须先拷贝这个数组的一个副本。

然后你可以在这个数组的副本里写入你要修改的数据,但是在这个过程中实际上你都是在操作一个副本而已。

这样的话,读操作是不是可以同时正常的执行?这个写操作对读操作是没有任何的影响的吧!

大家看下面的图,一起来体会一下这个过程:

img

关键问题来了,那那个写线程现在把副本数组给修改完了,现在怎么才能让读线程感知到这个变化呢?

关键点来了,划重点!这里要配合上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变量来赋值保证可见性,更新的时候对读线程没有任何的影响!

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。