多线程中如何安全发布对象
一、final域
对于final域,编译器和处理器要遵守两个重排序规则。
在构造函数内对一个final域的写入
与随后把这个被构造对象的引用赋值给一 个引用变量,这两个操作之间不能重排序。
初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操 作之间不能重排序。
这两个规则,可以防止指令重排序,来解决可见性问题。
1 写final域的重排序规则
i的值可能等于0,但j的值一定等于2
写普通变量i的操作,被编译器重排序到构造函数之外。
2 读final域的重排序规则
在一个线程中,初次读对象引用与初次读该对象包含的final 域,
JMM禁止处理器重排序这两个操作,编译器会在读final 域操作的前面插入一个LoadLoad屏障。
上面代码中,读普通域i代码,可以在写普通域i之前
3 溢出带来的重排序问题
在构造函数内部,不能让被构造对象的引用被其他线程可见。
分析:
构造函数中 i = 1 在底层不是一个原子操作
接着构造函数中 obj = this 将构造的对象发不出去,有可能 i = 1 还未操作完成
带来了溢出的问题。
final:只是保证了指令没有重排序。
总结
final域可以确保:
构造函数中初始化的值
- 写入值:构造函数中写入值不会被重排序。(重排序的话,赋值操作可能会在构造函数之外)
- 读取值:保证读取值的操作,在构造函数之后。
二、对象的发布与逃逸
在某些情况下:希望对象、以及对象的内部状态不被暴漏出去、有时为了正当的使用目的需要发布一个对象,这就涉及到安全发布。
发布的意思是:使一个对象能够被当前范围之外的代码所使用
对象溢出:一种错误的发布,当一个对象还没有构造完成时,就使它被其他线 程所见
安全发布对象4种方法:
- 在静态初始化函数中,初始化一个对象引用(为什么安全:因为是在类的初始化阶段去执行的,只能保证构造安全并不能保证后续使用的安全)
- 将对象的引用保存到volatile类型的域或者AtomicReference对象中(利 用volatile happen-before规则)[写在读之前]
- 将对象的引用保存到某个正确构造对象的final类型域中(防止变量的初始化重排序到构造函数之外)
- 将对象的引用保存到一个由锁保护的域中(读写都上锁)
Java中看到的指令并不代表指令底层的执行逻辑(可能有多个底层的指令),且多个指令会出现重排序的情况
例如:new VolatileSyncDemo();
- memory = allocate() :分配内存
- ctorInstance(memory) :初始化
- instance=memory :设置对象指向被分配的内存
DCL问题(double check lock):
有可能执行顺序: 1.3.2(会返回一个不完整实例),instance不为空,但是instance还没有完成初始化(修改方案二)
那么这个时候就返回了一个不完整的instance实例。
volatile作用:可以防止指令重排序,new VolatileSyncDemo():执行顺序1.2.3.能够返回一个完整的对象。
说明final域(final修饰的变量不会重排序到构造函数之外)
// 单例模式
public class VolatileSyncDemo {
private VolatileSyncDemo(){}
private volatile static VolatileSyncDemo instance=null;
// 问题代码:如在单例模式代码,有可能会创建出多个实例。
public static VolatileSyncDemo getInstance(){
if(instance==null){
instance = new VolatileSyncDemo();
}
return instance;
}
// 修改方案一:性能差,有可能instance不为空了,但是方法上还是有锁。
public static synchronized VolatileSyncDemo getInstance(){
if(instance==null){
instance = new VolatileSyncDemo();
}
return instance;
}
// 修改方案二:双重检查锁,缩小锁的范围,提高性能。
// 会有DCL问题(double check lock),变量添加volatile
public static VolatileSyncDemo getInstance(){
if(instance==null){
synchronized(VolatileSyncDemo.class) {
if(instance==null) {
instance = new VolatileSyncDemo();
}
}
}
return instance;
}
/**
* instance = new VolatileSyncDemo();
* ->
* 1. memory=allocate() :分配内存
* 2. ctorInstance(memory) :初始化
* 3. instance=memory :设置对象指向被分配的内存
*
*
* 1.3.2 (不完整实例)
*/
}
发布一个对象时:
- 会不会出现逃逸现象
- 是不是会存在不安全发布
-
- instance=memory :设置对象指向被分配的内存
- 1.3.2 (不完整实例)
*/
}
> 发布一个对象时:
>
> - 会不会出现逃逸现象
> - 是不是会存在不安全发布