CopyOnwrite 了解吗?

开发 前端
CopyOnWrite 只是看字面意思就能看出来,就是在写入时复制,说得轻巧,写入时复制,具体是怎么实现的呢?

 [[338419]]

本文转载自微信公众号「Java极客技术」,作者鸭血粉丝 。转载本文请联系Java极客技术公众号。  

概念

CopyOnWrite 只是看字面意思就能看出来,就是在写入时复制,说得轻巧,写入时复制,具体是怎么实现的呢?

先来说说思想,具体怎么实现等下分析

CopyOnWrite 的思想就是:当向一个容器中添加元素的时候,不是直接在当前这个容器里面添加的,而是复制出来一个新的容器,在新的容器里面添加元素,添加完毕之后再将原容器的引用指向新的容器,这样就实现了写入时复制

你还记得在提到数据库的时候,一般都会说主从复制,读写分离吗?CopyOnWrite 的设计思想是不是和经常说的主从复制,读写分离如出一撤?

优缺点

了解概念之后,对它的优缺点应该就比较好理解了

优点就是,读和写可以并行执行,因为读的是原来的容器,写的是新的容器,它们之间互不影响,所以读和写是可以并行执行的,在某些高并发场景下,可以提高程序的响应时间

但是呢,你也看到了, CopyOnWrite 是在写入的时候,复制了一个新的容器出来,所以要考虑它的内存开销问题,又回到了在学算法时一直强调的一个思想:拿空间换时间

需要注意一下,它只保证数据的最终一致性。因为在读的时候,读取的内容是原容器里面的内容,新添加的内容是读取不到的

基于它的优缺点应该就可以得出一个结论:CopyOnWrite 适用于写操作非常少的场景,而且还能够容忍读写的暂时不一致 如果你的应用场景不适合,那还是考虑使用别的方法来实现吧

还有一点需要注意的是:在写入时,它会复制一个新的容器,所以如果有写入需求的话,最好可以批量写入,因为每次写入的时候,容器都会进行复制,如果能够减少写入的次数,就可以减少容器的复制次数

在 JUC 包下,实现 CopyOnWrite 思想的就是 CopyOnWriteArrayList & CopyOnWriteArraySet 这两个方法,本篇文章侧重于讲清楚 CopyOnWriteArrayList

CopyOnWriteArrayList

在 CopyOnWriteArrayList 中,需要注意的是 add 方法:

  1. public boolean add(E e) { 
  2.         final ReentrantLock lock = this.lock; 
  3.         // 在写入的时候,需要加锁,如果不加锁的话,在多线程场景下可能会被 copy 出 n 个副本出来 
  4.         // 加锁之后,就能保证在进行写时,只有一个线程在操作 
  5.         lock.lock(); 
  6.         try { 
  7.             Object[] elements = getArray(); 
  8.             int len = elements.length; 
  9.             // 复制原来的数组 
  10.             Object[] newElements = Arrays.copyOf(elements, len + 1); 
  11.             // 将要添加的元素添加到新数组中 
  12.             newElements[len] = e; 
  13.             // 将对原数组的引用指向新的数组 
  14.             setArray(newElements); 
  15.             return true
  16.         } finally { 
  17.             lock.unlock(); 
  18.         } 
  19.     } 

在写的时候需要加锁,但是在读取的时候不需要添加

因为读取的是原数组的元素,对新数组没有什么影响,加了锁反而会增加性能开销

  1. public E get(int index) { 
  2.  return get(getArray(), index); 

举个例子:

  1. @Slf4j 
  2. public class ArrayListExample { 
  3.  
  4.     // 请求总数 
  5.     public static int clientTotal = 5000; 
  6.  
  7.     // 同时并发执行的线程数 
  8.     public static int threadTotal = 200; 
  9.  
  10.     private static List<Integer> list = new ArrayList<>(); 
  11.  
  12.     public static void  main(String[] args) throws Exception{ 
  13.         ExecutorService executorService = Executors.newCachedThreadPool(); 
  14.         final Semaphore semaphore = new Semaphore(threadTotal); 
  15.         final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); 
  16.         for (int i = 0; i < clientTotal; i++) { 
  17.             final int count = i; 
  18.             executorService.execute(()->{ 
  19.                 try { 
  20.                     semaphore.acquire(); 
  21.                     update(count); 
  22.                     semaphore.release(); 
  23.                 } catch (Exception e) { 
  24.                     log.error("exception",e); 
  25.                 } 
  26.                 countDownLatch.countDown(); 
  27.             }); 
  28.         } 
  29.         countDownLatch.await(); 
  30.         executorService.shutdown(); 
  31.         log.info("size:{}",list.size()); 
  32.     } 
  33.     private static void update(int i){ 
  34.         list.add(i); 
  35.     } 

上面是客户端请求 5000 次,有 200 个线程在同时请求,我使用的是 ArrayList 实现,咱们看下打印结果:

如果是线程安全的话,那么最后的结果应该是 5000 才对,多运行几次你会发现,每次程序的执行结果都是不一样的

如果是 CopyOnWriteArrayList 呢?

  1. @Slf4j 
  2. public class CopyOnWriteArrayListExample { 
  3.  
  4.     // 请求总数 
  5.     public static int clientTotal = 5000; 
  6.  
  7.     // 同时并发执行的线程数 
  8.     public static int threadTotal = 200; 
  9.  
  10.     private static List<Integer> list = new CopyOnWriteArrayList<>(); 
  11.  
  12.     public static void  main(String[] args) throws Exception{ 
  13.         ExecutorService executorService = Executors.newCachedThreadPool(); 
  14.         final Semaphore semaphore = new Semaphore(threadTotal); 
  15.         final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); 
  16.         for (int i = 0; i < clientTotal; i++) { 
  17.             final int count = i; 
  18.             executorService.execute(()->{ 
  19.                 try { 
  20.                     semaphore.acquire(); 
  21.                     update(count); 
  22.                     semaphore.release(); 
  23.                 } catch (Exception e) { 
  24.                     log.error("excepiton",e); 
  25.                 } 
  26.                 countDownLatch.countDown(); 
  27.             }); 
  28.         } 
  29.         countDownLatch.await(); 
  30.         executorService.shutdown(); 
  31.         log.info("size:{}",list.size()); 
  32.     } 
  33.     private static void update(int i){ 
  34.         list.add(i); 
  35.     } 

多运行几次,结果都是一样的:

由此可见, CopyOnWriteArrayList 是线程安全的

 

责任编辑:武晓燕 来源: Java极客技术
相关推荐

2022-07-11 10:47:46

容器JAVA

2021-04-27 05:57:12

ReadWriteLo容器

2022-07-26 00:00:22

HTAP系统数据库

2020-01-15 10:17:41

Kubernetes容器负载均衡

2014-04-17 16:42:03

DevOps

2012-09-06 17:54:28

2022-07-11 07:10:48

HTTP协议类型

2010-09-06 14:03:06

PPP身份认证

2018-02-02 10:56:19

屏蔽机房扩建

2021-01-15 07:44:21

SQL注入攻击黑客

2021-11-09 09:48:13

Logging python模块

2023-03-16 10:49:55

2019-10-31 08:36:59

线程内存操作系统

2019-09-16 08:40:42

2020-02-27 10:49:26

HTTPS网络协议TCP

2021-03-28 09:26:30

HttpHttp协议网络协议

2014-11-28 10:31:07

Hybrid APP

2021-01-12 12:07:34

Linux磁盘系统

2012-05-31 09:56:54

云安全

2018-11-21 09:32:10

IT云计算
点赞
收藏

51CTO技术栈公众号