(二)Java并发学习笔记--安全发布对象
逸出的方式
上边关于逸出的概念讲述的很是模糊,下面列举几个逸出的示例。
- 通过静态变量引用逸出
public static Set<Secret> knownSecrets;
public void initialize() {
knowsSecrets = new HashSet<Secret>();
}
上边代码示例中,调用initialize方法,发布了knowSecrets对象。当你向knowSecrets中添加一个Secret时,会同时将Secret对象发布出去,原因是可以通过遍历knowSecrets获取到Secret对象的引用,然后进行修改。
- 通过非静态(私有)方法
class UnsafeStates {
private String[] states = new String[]{"AK", "AL"};
public String[] getStates() {
return states;
}
}
以这种方式发布的states会出问题,任何一个调用者都能修改它的内容。数组states已经逸出了它所属的范围,这个本应该私有的数据,事实上已经变成共有的了。
- this逸出
public class ThisEscape {
public ThisEscape(EventSource source) {
source.registerListener(new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
});
}
}
在上边代码中,当我们实例化ThisEscape对象时,会调用source的registerListener方法时,便启动了一个线程,而且这个线程持有了ThisEscape对象(调用了对象的doSomething方法),但此时ThisEscape对象却没有实例化完成(还没有返回一个引用),所以我们说,此时造成了一个this引用逸出,即还没有完成的实例化ThisEscape对象的动作,却已经暴露了对象的引用,使其他线程可以访问还没有构造好的对象,可能会造成意料不到的问题。
通过上述示例,个人理解,对逸出的概念应该定义为:
一个对象,超出了它原本的作用域,而可以被其它对象进行修改,而这种修改及修改的结果是无法预测的。换句话说:一个对象发布后,它的状态应该是稳定的,修改是可被检测到的。如果在其它线程修改(或做其它操作)一个对象后导致对象的状态未知,就可以说这个对象逸出了。
总之,一个对象逸出后,不论其它线程或对象是否使用这个逸出的对象都不重要,重要的是,被误用及被误用后的未知结果的风险总是存在的。
PS 书中给出了避免this逸出的方法:
public class SafeListener {
private final EventListener listener;
private SafeListener() {
listener = new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
};
}
public static SafeListener newInstance(EventSource source) {
SafeListener safe = new SafeListener();
source.registerListener(safe.listener);
return safe;
}
}
在这个构造中,我们看到的最大的一个区别就是:当构造好了SafeListener对象之后,我们才启动了监听线程,也就确保了SafeListener对象是构造完成之后在使用的SafeListener对象。
对于这样的技术,书里面也有这样的注释:
具体来说,只有当构造函数返回时,this引用才应该从线程中逸出。构造函数可以将this引用保存到某个地方,只要其他线程不会在构造函数完成之前使用它。
安全发布对象
- 在静态初始化函数中初始化一个对象引用
- 将对象的应用保存到volatile类型的域或者AtomicReferance对象中
- 将对象的引用保存到某个正确构造对象的final类型域中
- 将对象的引用保存到一个由锁保护的域中。
/**
* 懒汉模式(线程不安全)
* 单例实例在第一次使用时进行创建
*/
@NotThreadSafe
public class SingletonExample1 {
// 私有构造函数
private SingletonExample1() {
}
// 单例对象
private static SingletonExample1 instance = null;
// 静态的工厂方法
public static SingletonExample1 getInstance() {
// 这里同时有两个线程进入就可能同时初始化两个对象
if (instance == null) {
instance = new SingletonExample1();
}
return instance;
}
}
懒汉模式本身是线程不安全的,如果想要实现线程安全可以通过synchronized关键字实现:
/**
* 懒汉模式
* 单例实例在第一次使用时进行创建
*/
@ThreadSafe
@NotRecommend
public class SingletonExample3 {
// 私有构造函数
private SingletonExample3() {
}
// 单例对象
private static SingletonExample3 instance = null;
// 静态的工厂方法
public static synchronized SingletonExample3 getInstance() {
if (instance == null) {
instance = new SingletonExample3();
}
return instance;
}
}
但此中方式不推荐使用,应该它通过同一时间内只允许一个线程来访问的方式实现线程安全,但是却带来了性能上面的开销。
我们可以通过以下方式来实现线程安全:
懒汉模式 -》 volatile + 双重同步锁单例模式
/**
* 懒汉模式 -》 双重同步锁单例模式
* 单例实例在第一次使用时进行创建
*/
@ThreadSafe
public class SingletonExample4 {
// 私有构造函数
private SingletonExample4() {
}
// 1、memory = allocate() 分配对象的内存空间
// 2、ctorInstance() 初始化对象
// 3、instance = memory 设置instance指向刚分配的内存
// JVM和cpu优化,发生了指令重排(多线程 )
// 1、memory = allocate() 分配对象的内存空间
// 3、instance = memory 设置instance指向刚分配的内存
// 2、ctorInstance() 初始化对象
// 单例对象 volatile + 双重检测机制 -> 禁止指令重排
private volatile static SingletonExample4 instance = null;
public static SingletonExample4 getInstance() {
if (instance == null) { // 双重检测机制 // B
synchronized (SingletonExample4.class) { // 同步锁
if (instance == null) {
instance = new SingletonExample4(); // A - 3
}
}
}
return instance;
}
}
/**
* 饿汉模式
* 单例实例在类装载时进行创建
*/
@ThreadSafe
public class SingletonExample2 {
// 私有构造函数
private SingletonExample2() {
}
// 单例对象
private static SingletonExample2 instance = new SingletonExample2();
// 静态的工厂方法
public static SingletonExample2 getInstance() {
return instance;
}
}
饿汉模式不会有线程问题,但是在类加载时实例化对象。使用时要考虑两点:
- 私有构造函数在使用时没有过多的逻辑处理(销毁性能,慢)
- 这个对象一定会被使用(浪费资源)
在静态代码块中实例化一个对象:
/**
* 饿汉模式
* 单例实例在类装载时进行创建
*/
@ThreadSafe
public class SingletonExample6 {
// 私有构造函数
private SingletonExample6() {
}
// 单例对象
private static SingletonExample6 instance = null;
static {
instance = new SingletonExample6();
}
// 静态的工厂方法
public static SingletonExample6 getInstance() {
return instance;
}
public static void main(String[] args) {
System.out.println(getInstance().hashCode());
System.out.println(getInstance().hashCode());
}
}
枚举模式:
/**
* 枚举模式:最安全
*/
@ThreadSafe
@Recommend
public class SingletonExample7 {
// 私有构造函数
private SingletonExample7() {
}
public static SingletonExample7 getInstance() {
return Singleton.INSTANCE.getInstance();
}
private enum Singleton {
INSTANCE;
private SingletonExample7 singleton;
// JVM保证这个方法绝对只调用一次
Singleton() {
singleton = new SingletonExample7();
}
public SingletonExample7 getInstance() {
return singleton;
}
}
}

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
Python L.sort 与sorted
L.sort() Definition : sort(key=None, reverse=False) Type : Function of None module L.sort(key=None, reverse=False) -> None -- stable sort IN PLACE sorted() Definition : sorted(iterable, *, key=None, reverse=False) Type : Function of builtins module Return a NEW sorted list from the items in iterable. *key* specifies a function of one argument that is used to extract a comparison key from each list element: key=str.lower. The default value is None (compare the elements directly). *reverse* is ...
-
下一篇
区块链开发公司为企业带来哪些商业机会
区块链盛行的时代,目前已经有不少企业推出自己的区块链产品,现在区块链产品市场中,有些技术厂商号称自己的产品是区块链,可实际上,也许只是用了分布式存储而已。离开激励机制和共识机制的区块链,实际上就是物联网的一种,而不具备区块链的实质。但因为缺乏行业标准和对技术实际应用的预判,人们也很难给出有效鉴别的必要。 第一个机会,硬件的开发。比如著名的华强北商业区,现在已经很少再销售我们传统认知的电脑、笔记本以及相关的电子产品了,而是借区块链这个新生势力,开始售卖区块链相关的硬件产品。很多炒币的人肯定都挖过矿,挖矿就一定需要电脑、路由器等。而我们正常使用的电脑,硬件和系统都不够用来挖矿,所以就有了这种专门为区块链挖矿而诞生的硬件电脑。 其实这里还衍生出来了一个附加的商业机会,“代挖矿业务”。大家也可以理解为代运营,就是由于你的硬件条件不给力,就付费租用我的场地、硬件和人工,替你来挖矿。 第二个商业机会,就是软件开发。现在很多平台都在研究这种技术,甚至连星巴克这种和区块链关联性不大的企业都在研究这项技术。想要研究这项技术,就需要独立的系统,那么就不可避免的需要软件开发。 第三个商业机会,交易平台的研发...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker容器配置,解决镜像无法拉取问题
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- 2048小游戏-低调大师作品
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- MySQL数据库在高并发下的优化方案
- Dcoker安装(在线仓库),最新的服务器搭配容器使用
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题