Java 并发系列九 : Copy-on-Write模式解决并发

前言

感谢王宝令老师在极客时间的并发课程

背景

在上一篇文章Immutability模式的时候,Java 里 String 这个类在实现 replace() 方法的时候, 并没有修改底层数组的结构,而是新创建了一个字符串,这种方式在解决不可变问题的时候经常用到.如果你深入思考这个方法,你会发现他本质是一种Copy-on-Write 方法。 顾名思义就是写时复制.

不可变对象的写操作往往都是使用Copy-on-Write 方法解决的,当然Copy-on-Write 模式的应用领域不局限于Immutability 模式.

Copy-on-Write 模式的应用领域

CopyOnWriteArrayList 和 CopyOnWriteArraySet 这两个Copy-on-Write 容器,他们背后的设计思想就是Copy-on-Write;通 过Copy-on-Write 这两个容器实现的读操作时无锁的,将读性能发挥到了极致.

除了Java 领域,Copy-on-Write 在操作系统领域也有广泛的应用.

类 Unix 的操作系统中 创建进程的API 是 fork(), 传统的 fork() 函数会创建父进程的一个完整副本, 例如父进程的地址空间现在用到了 1G 的内存,那么 fork() 子进程的时候要复制父进程整个进程的地址空间(占有 1G 内存)给子进程,这个过程是很耗时的。 而 Linux 中的 fork() 函数就聪明得多了,fork() 子进程的时候,并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间;只用在父进程或者子进程需要写入的时候才会复制地址空间,从而使父子进程拥有各自的地址空间。

本质上讲,父子进程的地址空间以及数据都是要隔离的,使用Copy-on-Write 更多的时体现一种延时策略,只有在真正需要复制的时候才复制而不是提前复制好.同时Copy-on-Write 还支持按需复制,所以Copy-on-Write 在操作系统领域能够提升性能。相比较而言,Java 提供的Java 容器,由于在修改的同时,会复制整个容器,所以在提升读操作性能的同时,是以内存复制为代价的。同样的应用Copy-on-Write ,不同的场景对性能的影响是不同的。

除了上面我们说的 Java 领域、操作系统领域,很多其他领域也都能看到 Copy-on-Write 的身影:Docker 容器镜像的设计是 Copy-on-Write,甚至分布式源码管理系统 Git 背后的设计思想都有 Copy-on-Write……

不过,Copy-on-Write 最大的应用领域还是在函数式编程领域。函数式编程的基础是不可变性,所以函数式编程里面所有的修改操作,都需要Copy-on-Write 来解决,所有数据的修改都需要复制一份,性能会不会成为瓶颈?你的担忧是有道理的,之所以函数式编程早年间没有兴起,性能绝对托了后腿,但是随着硬件性能的提升,性能问题已经变得慢慢可以接收了,而且,Copy-on-Write 也远不像Java 里的 CopyOnWriteArrayList 那样笨:整个数组都复制一遍。 Copy-on-Write 也是可以按需复制的。

CopyOnWriteArrayList 和 CopyOnWriteArraySet 这两个Copy-on-Write 容器在修改的时候会复制整个数组,所以如果容器经常被修改或者这个数组本身非常大,是不建议使用的,反之如果数组不大,读多写少,并且对读性能要求苛刻,使用Copy-on-Write 容器就非常好了。

一个真实案例

一个RPC框架,有点类似 Dubbo ,服务提供方是多实例分布式部署,所以服务的客户端在调用RPC的时候,会选定一个服务实例来调用。这个选定的过程本质上就是在做负载均衡,而做负载均衡的前提是客户端要有全部的路由信息,例如下图中,A服务的提供方有三个实例,分别是 192.168.1.1、192.168.1.2 和 192.168.1.3, 客户端在调用目标服务A的时候,也就需要从这三个实例中选出来一个,然后再通过RPC把请求发送选中的目标实例。

img

RPC框架的一个核心就是维护服务的路由关系,我们可以把服务的路由关系简化如下,当服务上线或者下线的时候,就需要更新客户端这张路由表,

img

首先分析一下程序如何实现:每次RPC 调用都需要通过负载均衡器计算目标服务器的IP 和端口号, 而负载均衡器需要通过路由表获取接口的所有路由信息。也就是说,每次RPC 调用的时候,都需要访问路由表,所以访问路由表这个操作的性能是要求很高的。不过路由表对数据的一致性要求不高,一个服务提供方从上线到反馈到客户端的路由表里,即便有5s钟,很多时候也是可以接受的。而且路由表是典型的读多写少的场景,写操作很少,只有再服务上下线或者服务宕机这些情况下出现。

通过以上的分析你就会发现,这不是正好符合Copy-on-Write应用场景嘛?读多写少、弱一致性。CopyOnWriteArrayList 和 CopyOnWriteArraySet 天生就适用这种场景啊。所 以下面代码,RouteTable 这个类内部我们通过ConcurrentHashMap>这个数据结构来描述路由表,ConcurrentHashMap 的 Key 是接口名,Value 是路由集合,这个路由集合我们用是 CopyOnWriteArraySet。

下面我们来思考,Router 该如何设计,服务方的每一次上线、下线都会更新路由表信息,这时候你有两种选择,一种是通过更新Router 的一个标识,如果这样做,那么所有访问该状态的地方都需要做同步,这样很影响性能,另外一种就是采用Immutability 模式, 每次上线、下线都创建新的Router 对象,或者删除对应的Router 对象。由于上线、下线都创建新的Router 对象或者删除对应的Router 对象。由于上线、下线的频率很低,所以后者是最好的选择。

RPC中服务路由简单功能的实现

总结

目前Copy-on-Write 在 Java 中知名度不是很高,很多人都无意中忽略了他,但是其实这才是最简单的并发解决方案。他是如此的简单,以至于Java 中的基本数据类型

String、Integer、Long 都是基于Copy-on-Write 实现的。

Copy-on-Write 是一种非常通用的解决方案,很多技术领域有广泛的应用,不过他也是有缺点的,就是消耗内存,每次修改都需要复制一份出来,好在随着垃圾回收技术的成熟以及硬件的发展,这种内存消耗已经渐渐可以接受了,所以如果在实际工作中,写的操作非常少,可以考虑Copy-on-Write ,效果不错。。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352

推荐阅读更多精彩内容