Java - 如何进行安全发布
首先让我简单解释一下所谓"发布"。
发布(publish),使对象可以在当前作用域之外的代码中可见,如果该对象被发布,则该对象的非私有域中引用的所有实例同样也会被发布。
不仅仅是作为一个field,当一个对象作为一个方法的参数或者在公有方法中作为返回引用,这都属于发布。
而相对地,对于错误的发布,我们将其称为逸出(escape)。
那么,什么是"错误的发布"? 比如发布导致封装性的破坏(可能直接导致无法安全地进行继承)、线程安全性问题(尤其是不变性条件的破坏)。
仅仅是修改了访问修饰,但可能导致难以预测的问题,并发编程时发布也变得尤为敏感。
那如何能避免逸出? 最简单的方法就是不发布。
线程封闭 -> http://alvez.blog.51cto.com/7711135/1549674
但总不能一直这样下去,资源的共享也是线程并发的一大优势,于是如何进行安全的发布显得非常重要。
那么不可变对象的发布是否也属于发布? 当然,这也是安全发布的一种策略。
(保证不可变 -> http://alvez.blog.51cto.com/7711135/1549811)
任何线程都可以在没有进行额外同步处理的情况下安全访问不可变对象。
但是不可变并不仅仅是final关键字那么简单,如果指向的对象是可变的则仍需要进行同步处理。
看一段代码,如果只是单线程应用,则几乎没有问题(其实问题还是有的),但是从并发的角度看,发布出来的holder对象甚至没有考虑可见性问题,而且对象尚未创建完成就已经发布,其他线程看到这个holder时将是不一致状态的holder:
1
2
3
4
5
6
7
|
public class StuffIntoPublic { public Holder holder; public void initialize() { holder = new Holder( 42 ); } } |
于是,为了应对状态不一致的情况,我们将Holder设计为...谁会想用这样的对象...
1
2
3
4
5
6
7
8
9
10
11
12
|
public class Holder { private int n; public Holder( int n) { this .n = n; } public void assertSanity() { if (n != n) throw new AssertionError( "This statement is false." ); } } |
既然如此,那如何安全并友好地对可变对象进行同步? 以下是几点建议:
使用静态初始化方法创建对象。
用volatile或者AtomicReference修饰对象,保证并发可见性。
使用锁进行保护。
用final修饰,即便不能保证不可变,也可以保证安全初始化,并且更易分析。
以下面的代码为例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
public class MonitorVehicleTracker { private final Map<String, MutablePoint> locations; public MonitorVehicleTracker(Map<String, MutablePoint> locations) { this .locations = deepCopy(locations); } public synchronized Map<String, MutablePoint> getLocations() { return deepCopy(locations); } public synchronized MutablePoint getLocation(String id) { MutablePoint loc = locations.get(id); return loc == null ? null : new MutablePoint(loc); } public synchronized void setLocation(String id, int x, int y) { MutablePoint loc = locations.get(id); if (loc == null ) throw new IllegalArgumentException( "No such ID: " + id); loc.x = x; loc.y = y; } private static Map<String, MutablePoint> deepCopy(Map<String, MutablePoint> m) { Map<String, MutablePoint> result = new HashMap<String, MutablePoint>(); for (String id : m.keySet()) result.put(id, new MutablePoint(m.get(id))); return Collections.unmodifiableMap(result); } } |
整个对象中只有一个locations,我们使用final修饰保证了其安全创建。
但只是这一点还不够,构造器中我们并没有直接将参数引用到location上,而是进行一次静态的deep copy,并使用Collections.unmodifiableMap将结果装饰一遍。
接着,我们在getter/setter中做了同步处理,这一点正是OO特性对并发良好支持的体现。
getLocation中我们获得location后并没有直接将其返回,而是重新创建一个新对象,以此防止逸出。
但是这并不成功,问题不在于这段代码,而是在于locations的泛型——MutablePoint上:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class MutablePoint { public int x, y; public MutablePoint() { x = 0 ; y = 0 ; } public MutablePoint(MutablePoint p) { this .x = p.x; this .y = p.y; } } |
由于locations中的元素存在问题,这样一来locations就不能算是安全的发布。
一个对象被发布,该对象中非私有的引用也会被发布,因此,对point也需要进行处理。
鉴于point的创建开销不大,我们只需要保证其不可变:
1
2
3
4
5
6
7
8
|
public class Point { public final int x, y; public Point( int x, int y) { this .x = x; this .y = y; } } |
Java - 如何进行安全发布的更多相关文章
- 谈谈JAVA中的安全发布
谈谈JAVA中的安全发布 昨天看到一篇文章阐述技术类资料的"等级",看完之后很有共鸣.再加上最近在工作中越发觉得线程安全性的重要性和难以捉摸,又掏出了<Java并发编程实战& ...
- Java高并发--安全发布对象
Java高并发--安全发布对象 主要是学习慕课网实战视频<Java并发编程入门与高并发面试>的笔记 发布对像:使一个对象能够被当前范围之外的对象使用. 对象逸出:一种错误的发布.当一个对象 ...
- 【适合公司业务】全网最详细的IDEA里如何正确新建【普通或者Maven】的Java web项目并发布到Tomcat上运行成功【博主强烈推荐】(类似eclipse里同一个workspace下【多个子项目】并存)(图文详解)
不多说,直接上干货! 首先,大家要明确,IDEA.Eclipse和MyEclipse等编辑器之间的新建和运行手法是不一样的. 如果是在Myeclipse里,则是File -> new -> ...
- 全网最详细的IDEA里如何正确新建普通的Java web项目并发布到Tomcat上运行成功【博主强烈推荐】(类似eclipse里同一个workspace下【一个子项目】并存)(图文详解)
不多说,直接上干货! 首先,大家要明确,IDEA.Eclipse和MyEclipse等编辑器之间的新建和运行手法是不一样的. 如果是在Myeclipse里,则是File -> new -> ...
- 全网最详细的Eclipse里如何正确新建普通的Java web项目并发布到Tomcat上运行成功【博主强烈推荐】(图文详解)
不多说,直接上干货! 首先,大家要明确,IDEA.Eclipse和MyEclipse等编辑器之间的新建和运行手法是不一样的. 如果是在Myeclipse里,则是File -> new -> ...
- 全网最详细的MyEclipse里如何正确新建普通的Java web项目并发布到Tomcat上运行成功【博主强烈推荐】(图文详解)
不多说,直接上干货! 首先,大家要明确,IDEA.Eclipse和MyEclipse等编辑器之间的新建和运行手法是不一样的. 如果是在eclipse里,则是File -> new -> ...
- MQTT介绍(3)java模拟MQTT的发布,订阅
MQTT目录: MQTT简单介绍 window安装MQTT服务器和client java模拟MQTT的发布,订阅 在此强调一下mqtt的使用场景: 1.不可靠.网络带宽小的网络 2.运行的设备CPU. ...
- Java里观察者模式(订阅发布模式)
创建主题(Subject)接口 创建订阅者(Observer)接口 实现主题 实现观察者 测试 总结 在公司开发项目,如果碰到一些在特定条件下触发某些逻辑操作的功能的实现基本上都是用的定时器 比如用户 ...
- java调用C# webService发布的接口
java调用C# webService发布的接口 java调用C# webService方式有很多种我这里只介绍一种 首先需要引入axis的jar包 axis的maven坐标如下 <depend ...
- java调用oracle数据库发布WebService
package com.hyan.service; import java.io.FileInputStream;import java.sql.Connection;import java.sql. ...
随机推荐
- 爆款AR游戏如何打造?网易杨鹏以《悠梦》为例详解前沿技术
本文来自网易云社区. 7月31日,2018云创大会游戏论坛在杭州国际博览中心103B圆满举行.本场游戏论坛聚焦探讨了可能对游戏行业发展有重大推动的新技术.新实践,如AR.区块链.安全.大数据等. 网易 ...
- BZOJ3638|CodeForces 280D k-Maximum Subsequence Sum
题目链接:戳我 一类典型模型.线段树模拟网络流+区间最大K段和. 因为不会写,所以参考了黄学长的博客.但是我觉得他说得不够详细,所以想好好地解释一下: 前置技能1:区间最大子段和 如果K=1的时候怎么 ...
- CF455C Civilization | luogu HXY造公园
题目链接: https://www.luogu.org/problemnew/show/P2195 http://codeforces.com/contest/455/problem/C 显然我们可以 ...
- 【English】20190430
Network security网络安全[ˈnetwɜːrk] [sɪˈkjʊrəti] Teradata Generic Security Service 通用安全服务[dʒəˈnerɪk] [s ...
- each和foreach的区别
each和foreach的区别是什么,我一直忘了还有这一茬,现在把这个总结一下,以备后用. 1.foreach是js的原生方法:each是jq的方法: 例如: var arr = ['mary','j ...
- memcached服务
介绍 它是一套数据缓存系统或软件 用于动态应用系统中缓存数据库的数据,减少数据库的访问压力,达到提升性能的效果,实际应用环境中多用于数据库的cache的应用.它是通过预分配指定的内存空间来存储数据 定 ...
- [ActionScript 3.0] 如何控制加载swf动画的播放与暂停
此方法适用于用as 1.0或者as2.0以及as3.0编译的swf,因为as1.0和as2.0编译的swf是AVM1Movie类型,因此需要通过类ForcibleLoader.as将其转换为versi ...
- jmeter聚合报告详解
聚合报告(aggregate report) 对于每个请求,它统计响应信息并提供请求数,平均值,最大,最小值,错误率,大约吞吐量(以请求数/秒为单位)和以kb/秒为单位的吞吐量. 吞吐量是以取样目标点 ...
- fetch网络请求 get 和 post
//在React Native中,使用fetch实现网络请求 /* fetch 是一个封装程度更高的网络API, 使用了Promise* Promise 是异步编程的一种解决方案* Promise 对 ...
- 苹果Air A1466进入系统黑屏
现象:苹果Air A1466笔记本安装Windows 7系统后,安装官网对应型号的bootcamp后,重启机器,在Windows滚动条完成后随即进入黑屏状态,安全模式能够进入,在安全模式下卸载删除显卡 ...