发布对象
使一个对象能够被当前范围之外的代码所使用的叫做发布对象
import java.util.Arrays;
/**
* 多线程的场景下无法保证其他线程对states的修改,所以这个对象是线程不安全的
*/
public class PublishNotSafe {
private String[] states = {"a", "b", "c"};
public String[] getStates() {
return states;
}
public static void main(String[] args) {
PublishNotSafe p = new PublishNotSafe();
System.out.println("1-" + Arrays.toString(p.getStates()));
// 1-[a, b, c]
p.getStates()[0] = "修改之后的值";
System.out.println("2-" + Arrays.toString(p.getStates()));
// 2-[修改之后的值, b, c]
}
}
那么如何保证对象发布的安全性呢,有以下四种方法
在静态初始化函数中初始化一个对象引用
将对象的引用保存到volatile类型或者AtomicReference对象中
将对象的引用保存到某个正确构造对象的final类型域中
将对象的引用保存到一个由锁保护的域中
安全发布对象的四种方法
下面以单例模式来说明如何安全发布对象
1.饿汉式单例
/**
* 饿汉式单例,线程安全
* 但是每次类初始化的时候就执行构造方法,可能会导致性能下降
*/
public class SingleHungry {
private static final SingleHungry SINGLE_HUNGRY = new SingleHungry();
private SingleHungry() {
}
public static SingleHungry getSingleHungryInstance() {
return SINGLE_HUNGRY;
}
}
2.懒汉式单例
/**
* 懒汉式单例,线程不安全
* 调用的时候才会发生初始化,性能略高
*/
public class SingleLazy {
private static SingleLazy SINGLE_LAZY ;
private SingleLazy() {
}
public static SingleLazy getSingleLazyInstance() {
// 问题出现在这里,当多线程的场景下没有保证线程安全
if (SINGLE_LAZY == null) {
SINGLE_LAZY = new SingleLazy();
return SINGLE_LAZY;
}
return SINGLE_LAZY;
}
}
3.懒汉式单例 + 双检锁
/**
* 双检锁
*/
public class SingleDoubleCheckLock {
private static SingleDoubleCheckLock instance;
private SingleDoubleCheckLock() {
}
// 1.memory = allocate() 分配对象的内存空间
// 2.ctorInstance() 初始化对象
// 3.instance = memory 设置instance指向刚分配的内存
// JVM和CPU进行优化,发生了指令重排序
// 1.memory = allocate() 分配对象的内存空间
// 3.instance = memory 设置instance指向刚分配的内存
// 2.ctorInstance() 初始化对象
public static SingleDoubleCheckLock getInstance() {
if (instance == null) { // 双重检测机制 // B线程执行到这里的时候发现A已经执行到new对象的步骤直接return了一个没有指针的对象
synchronized (SingleDoubleCheckLock.class) { // 同步锁
if (instance == null) {
instance = new SingleDoubleCheckLock(); // A线程 - 3
}
}
}
return instance;
}
}
4.懒汉式单例 + 双检锁 + volatile
/**
* 双检锁
*/
public class SingleDoubleCheckLock {
private static volatile SingleDoubleCheckLock instance;
private SingleDoubleCheckLock() {
}
// 1.memory = allocate() 分配对象的内存空间
// 2.ctorInstance() 初始化对象
// 3.instance = memory 设置instance指向刚分配的内存
public static SingleDoubleCheckLock getInstance() {
if (instance == null) {
synchronized (SingleDoubleCheckLock.class) {
if (instance == null) {
instance = new SingleDoubleCheckLock();
}
}
}
return instance;
}
}
5.枚举单例
public class SingleEnum {
public static void main(String[] args) {
SingleEnum s1 = SingleEnum.getInstance();
SingleEnum s2 = SingleEnum.getInstance();
System.out.println(s1);
System.out.println(s2);
}
private SingleEnum() {
}
public static SingleEnum getInstance() {
return Singleton.INSTANCE.getInstance();
}
private enum Singleton {
INSTANCE;
private SingleEnum singleton;
// JVM保证这个方法只会被调用一次
Singleton() {
singleton = new SingleEnum();
}
public SingleEnum getInstance() {
return singleton;
}
}
}
对象逸出
一种错误的发布。当一个对象还没有构造完成时,就使它被其他线程所见
/**
* 在EscapeNotSafe没有被正确构造完成时就已经被InnerClass引用到了,可能会出现问题
* thread2在EscapeNotSafe没有完全发布之前就已经看到它了
* 不应该直接new InnerClass(),应该采用专有的init方法
*/
public class EscapeNotSafe {
private int thisCanBeEscape = 0;
public EscapeNotSafe() {
// thread2
new InnerClass();
}
private class InnerClass {
public InnerClass() {
System.out.println(EscapeNotSafe.this.thisCanBeEscape);
}
}
public static void main(String[] args) {
// thread1
new EscapeNotSafe();
}
}