1.读多写少的场景下引发的问题?

2.引入 CopyOnWrite 思想解决问题!

3.CopyOnWrite思想在Kafka源码中的运用

今天聊一个非常硬核的技术知识,给大家分析一下CopyOnWrite思想是什么,以及在Java并发包中的具体体现,包括在Kafka内核源码中是如何运用这个思想来优化并发性能的。

这个CopyOnWrite在面试的时候,很可能成为面试官的一个杀手锏把候选人给一击必杀,也很有可能成为候选人拿下Offer的独门秘籍,是相对高级的一个知识。

1、读多写少的场景下引发的问题?

大家可以设想一下现在我们的内存里有一个ArrayList,这个ArrayList默认情况下肯定是线程不安全的,要是多个线程并发读和写这个ArrayList可能会有问题。

好,问题来了,我们应该怎么让这个ArrayList变成线程安全的呢?

有一个非常简单的办法,对这个ArrayList的访问都加上线程同步的控制。

比如说一定要在synchronized代码段来对这个ArrayList进行访问,这样的话,就能同一时间就让一个线程来操作它了,或者是用ReadWriteLock读写锁的方式来控制,都可以。

我们假设就是用ReadWriteLock读写锁的方式来控制对这个ArrayList的访问。

这样多个读请求可以同时执行从ArrayList里读取数据,但是读请求和写请求之间互斥,写请求和写请求也是互斥的。

大家看看,代码大概就是类似下面这样:

public Object  read() {lock.readLock().lock();// 对ArrayList读取lock.readLock().unlock();
}
public void write() {lock.writeLock().lock();// 对ArrayList写lock.writeLock().unlock();
}
复制代码

大家想想,类似上面的代码有什么问题呢?

最大的问题,其实就在于写锁和读锁的互斥。假设写操作频率很低,读操作频率很高,是写少读多的场景。

那么偶尔执行一个写操作的时候,是不是会加上写锁,此时大量的读操作过来是不是就会被阻塞住,无法执行?

这个就是读写锁可能遇到的最大的问题。

2、引入 CopyOnWrite 思想解决问题

这个时候就要引入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变量来赋值保证可见性,更新的时候对读线程没有任何的影响!

3、CopyOnWrite 思想在Kafka源码中的运用

在Kafka的内核源码中,有这么一个场景,客户端在向Kafka写数据的时候,会把消息先写入客户端本地的内存缓冲,然后在内存缓冲里形成一个Batch之后再一次性发送到Kafka服务器上去,这样有助于提升吞吐量。

话不多说,大家看下图:

这个时候Kafka的内存缓冲用的是什么数据结构呢?大家看源码:

private final ConcurrentMap<TopicPartition, Deque<RecordBatch>> batches =
new CopyOnWriteMap<TopicPartition, Deque<RecordBatch>>();
复制代码

这个数据结构就是核心的用来存放写入内存缓冲中的消息的数据结构,要看懂这个数据结构需要对很多Kafka内核源码里的概念进行解释,这里先不展开。

但是大家关注一点,他是自己实现了一个CopyOnWriteMap,这个CopyOnWriteMap采用的就是CopyOnWrite思想。

我们来看一下这个CopyOnWriteMap的源码实现:

 // 典型的volatile修饰普通Mapprivate volatile Map<K, V> map;@Overridepublic synchronized V put(K k, V v) {// 更新的时候先创建副本,更新副本,然后对volatile变量赋值写回去Map<K, V> copy = new HashMap<K, V>(this.map);V prev = copy.put(k, v);this.map = Collections.unmodifiableMap(copy);return prev;}@Overridepublic V get(Object k) {// 读取的时候直接读volatile变量引用的map数据结构,无需锁return map.get(k);}
复制代码

所以Kafka这个核心数据结构在这里之所以采用CopyOnWriteMap思想来实现,就是因为这个Map的key-value对,其实没那么频繁更新。

也就是TopicPartition-Deque这个key-value对,更新频率很低。

但是他的get操作却是高频的读取请求,因为会高频的读取出来一个TopicPartition对应的Deque数据结构,来对这个队列进行入队出队等操作,所以对于这个map而言,高频的是其get操作。

这个时候,Kafka就采用了CopyOnWrite思想来实现这个Map,避免更新key-value的时候阻塞住高频的读操作,实现无锁的效果,优化线程并发的性能。

相信大家看完这个文章,对于CopyOnWrite思想以及适用场景,包括JDK中的实现,以及在Kafka源码中的运用,都有了一个切身的体会了。

如果你能在面试时说清楚这个思想以及他在JDK中的体现,并且还能结合知名的开源项目 Kafka 的底层源码进一步向面试官进行阐述,面试官对你的印象肯定大大的加分。

END

欢迎长按下图关注公众号:石杉的架构笔记!

公众号后台回复资料,获取作者独家秘制学习资料

石杉的架构笔记,BAT架构经验倾囊相授

多读少写的场景 如何提高性能相关推荐

  1. 【Java面试高频-集合】- 读写的场景设计集合是怎么样?对于读多写少要如何设计的呢?对于读少写多又该如何设计呢?

    Java集合-读写的场景 1 读多写少的场景 **CopyOnWrite的思想.**读数据不加锁,写数据的时候利用拷贝的副本来执行. ReadWriteLock的思想也可以. 那么 写线程现在把副本数 ...

  2. 数据库设计 读多写少、写多读少、写多读多各场景数据库建设方案

    数据库建设方案 读多写少 写多读少 写多读多 数据库集群方案优缺点 读多写少 解决方案:采用传统关系型数据库足以应对,若并发量很大,采用mysql集群即可应对! 写多读少 1.业务场景:滴滴.饭堂刷卡 ...

  3. 数据库优化专题---4、读多写少和读多写多

    数据库优化专题-1.表的主键用数字还是UUID 数据库优化专题-2.逻辑删除还是物理删除 数据库优化专题-3.千万记录如何快速分页 数据库优化专题-4.读多写少和读多写多 数据库优化专题-5.删改数据 ...

  4. 一读多写非自旋无锁链表队列实现

    现在一说到多线程操作的队列,基本上都会标榜自己是无锁的,这样的无锁基本上都有两大特点: 1. 换了一种锁 利用原子变量自旋,本质还是自旋等锁,其实也是一种锁,只是用了更底层一点.这种无锁,其实也是消耗 ...

  5. Golang sync.Map 原理(两个map实现 读写分离、适用读多写少场景)

    参考: 由浅入深聊聊Golang的sync.Map 通过对源码的逐行分析,清晰易懂 Golang sync.Map原理 通过向 sync.Map 中增删改查来介绍sync.Map的底层原理 Golan ...

  6. ReentrantReadWriteLock读写锁(读多写少场景)

    ReentrantReadWriteLock读写锁 适合读多写少的场景. 读锁ReentrantReadWriteLock.ReadLock可以被多个线程同时持有, 所以并发能力很高. 写锁Reent ...

  7. ES7.5升级7.17后在写多读少场景下CPU、IO飙升

    背景 1.ES PAAS管理的集群升级了100+,从7.5升级到7.17 (保证每个大版本最终仅维护一个小版本集群) 2.由于业务使用差异大,也出了不少问题,前面的文章也有提到过Integer类型字段 ...

  8. 面试官问:在读多写少的情况下,如何优化 MySQL 的数据查询方案

    作者 | 面试官问     责编 | 张文 来源 | 面试官问(ID:interviewer_asked) 面试官问:假设你负责的某业务在双十一期间要搞运营活动,公司投入了大量的营销费用进行推广,此举 ...

  9. Python实现读、写、改Excel文件的常见方式及其应用场景对比

    Excel表格类型 当前,Excel文件主要有如下两种格式: .xls格式,主要应用于Excel 2003及以下版本. .xlsx格式,主要应用于Excel 2007及以上版本. 读取Excel xl ...

  10. 多读多写是提高写作水平的重要保证

    以下内容由南通 学成教育咨询有限公司 http://www.ntxcj.com/提供:     作文和基础知识.阅读理解并称为语文教学的三大板块,是检验学生语文综合水平.母语把握能力及综合素养的标准. ...

最新文章

  1. 28自定义View 模仿联系人字母侧栏
  2. mysql崩溃恢复过程_一起看下MySQL的崩溃恢复到底是怎么回事
  3. ResNets首次反超有监督学习!DeepMind用自监督实现逆袭,无需标注
  4. 解决Redhat Linux AS使用yum时出现This system is not registered with RHN的问题(改用CentOS的yum)...
  5. 面试常用shell脚本_Shell脚本编写及常见面试题
  6. flutter_webview_plugin 无法加载网页的异常处理
  7. php代码怎么复制_PHP_PHP网站备份程序代码分享,效果图:PHP代码 复制代码 代码 - phpStudy...
  8. JavaScript判断字符串中包含另一个字符串(QML 中使用)
  9. Hadoop点滴-HDFS命令行接口
  10. UVa12633-Super Rooks on Chessboard-容斥+FFT
  11. [css] css图片缩放失真出现锯齿的如何解决呢?
  12. LeetCode MySQL 184. 部门工资最高的员工
  13. lucene学习之helloworld(简单实例)
  14. boost采取什么驱动电路_当我们只是采取积极的意愿时会发生什么?
  15. Java 多线程 之 wait等待 线程实例
  16. 史诗级中日韩新字体诞生:思源黑体(Source Han Sans)
  17. pg数据库表存放在哪里_pg数据库系统表
  18. m3云服务器_“中国球迷”索尼A7RM3及镜头下的情况肖像
  19. Camtasia“喀秋莎”2022一款录屏神器
  20. 宝塔上设置阿里云code的git管理

热门文章

  1. 拉盖尔多项式的正交性
  2. 百旺如何看是否清卡_【问吧】如何查看是否清卡成功,出现这些问题,如何处理?...
  3. pytorch 模型输出特征 保存npy
  4. k8s部署nacos2.0.3出现tried: server is DOWNnow, detailed error message: Optional[Distro protocol XXXX
  5. 千兆路由器和百兆路由器
  6. Unity 通过修改图片透明度实现淡出效果
  7. java流分类_什么是流分类-JAVA中什么是流?流经常按照哪几种方式分类,每种方式又将流各分? 爱问知识人...
  8. SuperMap iObject入门开发系列七管线横断面分析
  9. Proteus8.9 VSM Studio PIC编译器仿真PIC16F887A_系列a02_PIC_GPS数据接受反馈HTC仿真
  10. 印尼穴居小矮人是进化异类还是病态现代人?