java copy-on-write_[Java并发-18-并发设计模式] COW模式:Copy-on-Write模式的应用领域
在上一篇文章中我们讲到 Java 里 String 这个类在实现 replace() 方法的时候,并没有更改原字符串里面 value[] 数组的内容,而是创建了一个新字符串,这种方法在解决不可变对象的修改问题时经常用到。如果你深入地思考这个方法,你会发现它本质上是一种Copy-on-Write 方法。所谓 Copy-on-Write,经常被缩写为 COW 或者 CoW,顾名思义就是写时复制。
不可变对象的写操作往往都是使用 Copy-on-Write 方法解决的,当然 Copy-on-Write 的应用领域并不局限于 Immutability 模式。下面我们先简单介绍一下 Copy-on-Write 的应用领域,让你对它有个更全面的认识。
Copy-on-Write 模式的应用领域
我们知道 CopyOnWriteArrayList 和 CopyOnWriteArraySet 这两个 Copy-on-Write 容器,它们背后的设计思想就是 Copy-on-Write;通过 Copy-on-Write 这两个容器实现的读操作是无锁的,由于无锁,所以将读操作的性能发挥到了极致。
除了上面我们说的 Java 领域,很多其他领域也都能看到 Copy-on-Write 的身影:Docker 容器镜像的设计是 Copy-on-Write,甚至分布式源码管理系统 Git 背后的设计思想都有 Copy-on-Write。
CopyOnWriteArrayList 和 CopyOnWriteArraySet 这两个 Copy-on-Write 容器在修改的时候会复制整个数组,所以如果容器经常被修改或者这个数组本身就非常大的时候,是不建议使用的。反之,如果是修改非常少、数组数量也不大,并且对读性能要求苛刻的场景,使用 Copy-on-Write 容器效果就非常好了。
一个真实案例
RPC 框架中有个基本的核心功能就是负载均衡。服务提供方是多实例分布式部署的,所以服务的客户端在调用 RPC 的时候,会选定一个服务实例来调用,这个选定的过程本质上就是在做负载均衡,而做负载均衡的前提是客户端要有全部的路由信息。
例如在下图中,A 服务的提供方有 3 个实例,分别是 192.168.1.1、192.168.1.2 和 192.168.1.3,客户端在调用目标服务 A 前,首先需要做的是负载均衡,也就是从这 3 个实例中选出 1 个来,然后再通过 RPC 把请求发送选中的目标实例。
RPC 路由关系图
RPC 框架的一个核心任务就是维护服务的路由关系,我们可以把服务的路由关系简化成下图所示的路由表。当服务提供方上线或者下线的时候,就需要更新客户端的这张路由表。
每次 RPC 调用都需要通过负载均衡器来计算目标服务的 IP 和端口号,而负载均衡器需要通过路由表获取接口的所有路由信息,也就是说,每次 RPC 调用都需要访问路由表,所以访问路由表这个操作的性能要求是很高的。不过路由表对数据的一致性要求并不高,一个服务提供方从上线到反馈到客户端的路由表里,即便有 5 秒钟,很多时候也都是能接受的。而且路由表是典型的读多写少类问题。
通过以上分析,你会发现一些关键词:对读的性能要求很高,读多写少,弱一致性。它们综合在一起,你会想到什么呢?CopyOnWriteArrayList 和 CopyOnWriteArraySet 天生就适用这种场景啊。所以下面的示例代码中,RouteTable 这个类内部我们通过ConcurrentHashMap>这个数据结构来描述路由表,ConcurrentHashMap 的 Key 是接口名,Value 是路由集合,这个路由集合我们用是 CopyOnWriteArraySet。
下面我们再来思考 Router 该如何设计,服务提供方的每一次上线、下线都会更新路由信息,这时候你有两种选择。
一种是通过更新 Router 的一个状态位来标识,如果这样做,那么所有访问该状态位的地方都需要同步访问,这样很影响性能。
另外一种就是采用 Immutability 模式,每次上线、下线都创建新的 Router 对象或者删除对应的 Router 对象。由于上线、下线的频率很低,所以后者是最好的选择。
Router 的实现代码如下所示,是一种典型 Immutability 模式的实现,需要你注意的是我们重写了 equals 方法,这样 CopyOnWriteArraySet 的 add() 和 remove() 方法才能正常工作。
// 路由信息
public final class Router{
private final String ip;
private final Integer port;
private final String iface;
// 构造函数
public Router(String ip,
Integer port, String iface){
this.ip = ip;
this.port = port;
this.iface = iface;
}
// 重写 equals 方法
public boolean equals(Object obj){
if (obj instanceof Router) {
Router r = (Router)obj;
return iface.equals(r.iface) &&
ip.equals(r.ip) &&
port.equals(r.port);
}
return false;
}
public int hashCode() {
// 省略 hashCode 相关代码
}
}
// 路由表信息
public class RouterTable {
//Key: 接口名
//Value: 路由集合
ConcurrentHashMap>
rt = new ConcurrentHashMap<>();
// 根据接口名获取路由表
public Set get(String iface){
return rt.get(iface);
}
// 删除路由
public void remove(Router router) {
Set set=rt.get(router.iface);
if (set != null) {
set.remove(router);
}
}
// 增加路由
public void add(Router router) {
Set set = rt.computeIfAbsent(
route.iface, r ->
new CopyOnWriteArraySet<>());
set.add(router);
}
}
总结
其实 Copy-on-Write 才是最简单的并发解决方案。它是如此简单,以至于 Java 中的基本数据类型 String、Integer、Long 等都是基于 Copy-on-Write 方案实现的。
Copy-on-Write 是一项非常通用的技术方案,在很多领域都有着广泛的应用。不过,它也有缺点的,那就是消耗内存,每次修改都需要复制一个新的对象出来。如果写操作非常少,那你就可以尝试用一下 Copy-on-Write,效果还是不错的。
java copy-on-write_[Java并发-18-并发设计模式] COW模式:Copy-on-Write模式的应用领域相关推荐
- Java并发编程-并发工具包(java.util.concurrent)使用指南(全)
1. java.util.concurrent - Java 并发工具包 Java 5 添加了一个新的包到 Java 平台,java.util.concurrent 包.这个包包含有一系列能够让 Ja ...
- java list 占用内存不释放_Java并发编程 - CopyOnWrite容器类
前言 当我们对List进行遍历的时候,如果list被修改了会抛出java.util.ConcurrentModificationException错误.那么有没有办法在遍历一个list的时候,还向li ...
- 一篇博客带你轻松应对java面试中的多线程与高并发
1. Java线程的创建方式 (1)继承thread类 thread类本质是实现了runnable接口的一个实例,代表线程的一个实例.启动线程的方式start方法.start是一个本地方法,执行后,执 ...
- Java并发编程-并发工具包java.util.concurrent使用指南
译序 本指南根据 Jakob Jenkov 最新博客翻译,请随时关注博客更新 本指南已做成中英文对照阅读版的 pdf 文档,有兴趣的朋友可以去 Java并发工具包java.util.concurren ...
- java await signal_【Java并发008】原理层面:ReentrantLock中 await()、signal()/signalAll()全解析...
一.前言 上篇的文章中我们介绍了AQS源码中lock方法和unlock方法,这两个方法主要是用来解决并发中互斥的问题,这篇文章我们主要介绍AQS中用来解决线程同步问题的await方法.signal方法 ...
- java 多线程操作map_Java 多线程中ConcurrentHashMap并发读写操作范例
范例1: package com.contoso; import java.util.Random; import java.util.UUID; import java.util.concurren ...
- java 并发(并发工具包)
java 并发(并发工具包) ##13个原子操作类 ####原子基本类型 AtomicBoolean AtomicInteger AtomicLong 常用方法如下: int addAndGet(in ...
- 【2022最新Java面试宝典】—— Java并发编程面试题(123道含答案)
目录 一.基础知识 1. 为什么要使用并发编程 2. 多线程应用场景 3. 并发编程有什么缺点 4. 并发编程三个必要因素是什么? 5. Java 程序中怎么保证多线程的运行安全? 6. 并行和并发有 ...
- java fork_浅谈Java的Fork/Join并发框架
前几天有写到整合并发结果的文章,于是联想到了Fork/Join.因为在我看来整合并发结果其实就是Fork/Join中的Join步骤.所以今天我就把自己对Fork/Join一些浅显的理解记录下来. 1. ...
最新文章
- C++中一些类和数据结构的大小的总结
- python编程 语言-Python——最美丽的编程语言
- 如果把线程当作一个人来对待,所有问题都瞬间明白了
- mongodb防火墙配置
- [转载] Python numpy函数:all()和any()比较矩阵
- linux安装svn(yum安装)
- 《深入浅出数据分析》资源汇总
- 概率论的学习和整理11:伯努利试验的3种分布:0-1分支,几何分布, 二项分布
- Silverlight新型的富媒体
- Overlay网络与物理网络的关系
- Python学习培训方法
- ips细胞技术治疗尿毒症最新进展
- Google VR开发-Cardboard VR SDK头部追踪实现(罗德里格旋转公式)
- 谷歌小恐龙作弊+作死方法
- 来自菜鸡的前端权限简单实现
- 名博是怎样炼成的——读后感
- 5G的五项核心技术和5.5G相关的技术
- PAT 乙级 1086 python
- 后端接口返回一张图片
- Vite打包项目提示“some chunks are larger than 500 kib....“
热门文章
- pandas Dataframe/Series 设置保留小数位数
- 关于php的函数,总结关于PHP文件函数有哪些
- java 证书公钥 私钥_java#keytool#生成私钥证书库、公钥证书库
- redis 参数配置总结
- 【C++对象模型】第一章 关于对象
- Velocity教程 (zhuan)
- mysql查询锁表及解锁
- (转)Flex4中的皮肤(2):Skin State
- tika提取html,TIKA内容提取
- matlab乘幂的指数是矩阵,信号与系统MATLAB基本语法.ppt