Java多线程编程之不可变对象模式
在多线程环境中,为了保证共享数据的一致性,往往需要对共享数据的使用进行加锁,但是加锁操作本身就会带来一定的开销,这里可以使用将共享数据使用不可变对象进行封装,从而避免加锁操作。
1. 模型角色
不可变对象指的是,对象内部没有提供任何可供修改对象数据的方法,如果需要修改共享变量的任何数据,都需要先构建整个共享对象,然后对共享对象进行整体的替换,通过这种方式来达到对共享对象数据一致性的保证。如下是不可变对象设计的类图:

如下是各个角色功能的描述:
- ImmutableObject:不可变对象的载体。对于需要一致性更改的数据,都需要放入不可变对象中,对于不可变对象,需要注意如下几点:
- 不可变对象的属性必须使用final修饰,以防止属性被意外修改,并且final还可以保证JVM在该对象构造完成时该属性已经初始化成功(JVM在构造完对象时可能只是为其分配了引用空间,而各个属性值可能还未初始化完成);
- 属性的设值必须在构造方法中统一构造完成,其余的方法只是提供的查询各个属性相关的方法;
- 对于可变状态的引用类型属性,如集合,在获取该类型的属性时,必须返回该属性的一个深度复制结果,以防止不可变对象的该属性值被客户端修改;
- 不可变对象的类必须使用final修饰,以防止子类对其本身或其方法进行修改;
- Manipulator:聚合对象的管理类(某些情况可不用)。对于聚合对象的管理,主要有两部分:查询和修改。对于聚合对象的查询,只需要根据一定的规则在Manipulator类中获取该对象即可,对于聚合对象的修改,需要首先通过参数构造一个完整的聚合对象,然后将保存的该聚合对象的引用进行替换即可;
- Client:获取聚合对象的客户端应用。
2. 使用场景
对于不可变对象,其主要有如下三种使用场景:
- 当某组数据变化不是很频繁,则可以使用不可变对象。对于数据的访问,由于不可变对象的引用空间不会发生变化,因而任何线程都可以保有同一个不可变对象的引用,这样可以减少内存的消耗,也能保证数据访问的一致性;
- 当某组数据需要进行一致性的更新操作时,可以使用不可变对象。由于不可变对象能够保证对其任何数据的修改都是对整个对象的替换,因而其能够保证整组数据的一致性。需要注意的是,如果该组数据变更比较频繁,则不宜使用不可变对象,因为这会造成创建大量的不可变对象,从而增加JVM垃圾回收的压力。具体的情况应根据JVM可使用内存大小与对象更新的频率进行考量;
- 当需要对象作为Map的键时可以使用不可变对象。对于Map而言,其hashCode()方法默认返回的是对象的引用地址,而对于不可变对象而言,由于其引用地址是不会发生变化的,因而即使不对其hashCode()方法进行重写,其也不会发生变化。
3. 使用示例
对于不可变对象,一个很好的例子就是地址经纬度。笔者所工作的公司处理的业务和房源相关,其中有一部分就是需要处理房源所在点的经纬度信息,这里就可以使用不可变对象,因为房源经纬度基本上不会发生变化,并且对其操作也主要是以查询为主,最重要的是,对经纬度的处理必须是经度和纬度同时发生变化,任何情况下只更改了其中一个数据都会产生问题。如下是记录房源经纬度的类:
public final class Location {
private final long id;
private final String latitude;
private final String longitude;
public Location(long id, String latitude, String longitude) {
this.id = id;
this.latitude = latitude;
this.longitude = longitude;
}
public long getId() {
return id;
}
public String getLatitude() {
return latitude;
}
public String getLongitude() {
return longitude;
}
}
该Location类也即上述UML类图中的ImmutableObject部分。可以看到,任何对Location对象的修改都必须重新构建一个Location对象。如下是对Location的管理类,用于存储具体的Location信息的:
public class LocationHolder {
private final LocationHolder INSTANCE = new LocationHolder();
private Map<Long, Location> locations;
private LocationHolder() {
this.locations = new ConcurrentHashMap<>();
}
public LocationHolder getInstance() {
return INSTANCE;
}
public Location getLocation(long id) {
return locations.get(id);
}
public void addLocation(long id, String latitude, String longitude) {
Location location = new Location(id, latitude, longitude);
locations.put(id, location);
}
public Map<Long, Location> getLocations() {
return Collections.unmodifiableMap(locations);
}
}
可以看到,这里对Location的管理是通过一个单例类LocationHolder进行的,任何对Location的操作都进行了封装,并且这里批量获取Location,也是返回了一个不可变Map,从而保证原始数据不会作任何修改,如果该Map的键或值任何一方可能发生变化,那么在返回值则必须返回一个深度复制的结果,这样才能保证原始数据的完整性。
Java多线程编程之不可变对象模式的更多相关文章
- Java多线程编程(二)对象及变量的并发访问
一.synchronized同步方法 1.方法内的变量为线程安全 “非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全”问题,所得结果也就是“线程安全”的了. 示例: ...
- java多线程编程模式
前言 区别于java设计模式,下面介绍的是在多线程场景下,如何设计出合理的思路. 不可变对象模式 场景 1. 对象的变化频率不高 每一次变化就是一次深拷贝,会影响cpu以及gc,如果频繁操作会影响性能 ...
- Java多线程编程模式实战指南(二):Immutable Object模式--转载
本文由本人首次发布在infoq中文站上:http://www.infoq.com/cn/articles/java-multithreaded-programming-mode-immutable-o ...
- Java多线程编程核心技术---对象及变量的并发访问(二)
数据类型String的常量池特性 在JVM中具有String常量池缓存的功能. public class Service { public static void print(String str){ ...
- Java多线程编程中Future模式的详解
Java多线程编程中,常用的多线程设计模式包括:Future模式.Master-Worker模式.Guarded Suspeionsion模式.不变模式和生产者-消费者模式等.这篇文章主要讲述Futu ...
- Java多线程编程核心技术(二)对象及变量的并发访问
本文主要介绍Java多线程中的同步,也就是如何在Java语言中写出线程安全的程序,如何在Java语言中解决非线程安全的相关问题.阅读本文应该着重掌握如下技术点: synchronized对象监视器为O ...
- Java多线程编程中Future模式的详解<转>
Java多线程编程中,常用的多线程设计模式包括:Future模式.Master-Worker模式.Guarded Suspeionsion模式.不变模式和生产者-消费者模式等.这篇文章主要讲述Futu ...
- Java多线程编程详解
转自:http://programming.iteye.com/blog/158568 线程的同步 由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题.Ja ...
- Java多线程编程核心技术
Java多线程编程核心技术 这本书有利于对Java多线程API的理解,但不容易从中总结规律. JDK文档 1. Thread类 部分源码: public class Thread implements ...
随机推荐
- (转)添加Template(模板)并基于模板部署应用
通过Template,可以定义一个或多个需要部署的镜像,定义依赖的对象,定义可供用户输入的配置参数项. 以cakephp-mysql.json为例. # oc create -f https://ra ...
- JavaScript跨源资源共享
CORS(跨 源资源共享)基本思想,就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应式应该成功还是失败 IE对CORS的实现 IE8引入了XDR类型,与XHR类似,但可以实现安 ...
- #leetcode刷题之路45-跳跃游戏 II
给定一个非负整数数组,你最初位于数组的第一个位置.数组中的每个元素代表你在该位置可以跳跃的最大长度.你的目标是使用最少的跳跃次数到达数组的最后一个位置. 示例:输入: [2,3,1,1,4]输出: 2 ...
- #leetcode刷题之路39-组合总和
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合.candidates 中的数字可以无限制重复被选取 ...
- maven第二天——重要概念与其它操作
一.在eclipse中建立工程 在day01中我们搭建了eclipse的maven环境,接下来我们开始建立maven项目 1.在eclipse中建立JAVA工程 file->new->ma ...
- Android 使用系统自带分享功能
way1: Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain");// setT ...
- 【转载】COM 组件设计与应用(四)——简单调用组件
原文:http://vckbase.com/index.php/wv/1211.html 一.前言 同志们.朋友们.各位领导,大家好. VCKBASE 不得了, 网友众多文章好. 组件设计怎么学? 知 ...
- Bash shell 常用快捷键
1,ctrl+c :强制终止当前命令 2,ctrl+l :清屏 3,ctrl+a :光标移动到行首 4,ctrl+e :光标移动到行尾 5,ctrl+u :从光标所在位置删除到行首 6 ...
- 3 huigu
w +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 组件复用 --------------- ...
- 洛咕 P4131 [WC2005]友好的生物
洛咕 P4131 [WC2005]友好的生物 首先可以发现\(C\)是没有用的,可以乘进所有的权值里面做 考虑没有最后一维的限制,那么两个生物的友好值就是 \(\sum_{i=1}^k|a_i-b_i ...