安全的发布
四种方法
- 在静态初始化函数中初始化一个对象引用
- 将对象的引用保存到volatile类型域或者AtomicReference对象中
- 将对象的引用保存到某个正确构造对象的final类型域中
- 将对象的引用保存到一个由锁保护的域中
分析
- 不让其他程序new该类对象,咋办?——可以将类中的构造函数私有化。
- 在本类中new一个对象。
- 定义一个方法返回该对象,让其他程序可以获取到。之所以定义方法访问,就是为了可控。
- 要考虑指令重排序的情况
饿汉式单例模式
final成员方式
public class Single{// 创建一个本类对象private static final Single s = new Single();// 构造函数私有化private Single(){}// 定义一个方法返回该对象,让其他程序可以获取到。之所以定义方法访问,就是为了可控public static Single getInstance(){return s;}}
静态块方式
错误方式
这个静态代码块先执行, 然后走到成员赋值, 被覆盖为null了, 对象是null
import com.mmall.concurrency.annoations.ThreadSafe;/*** 饿汉模式* 单例实例在类装载时进行创建*/public class SingletonExample6 {// 私有构造函数private SingletonExample6() {}static {instance = new SingletonExample6();}// 单例对象private static SingletonExample6 instance = null;// 静态的工厂方法public static SingletonExample6 getInstance() {return instance;}public static void main(String[] args) {System.out.println(getInstance().hashCode());System.out.println(getInstance().hashCode());}}
正确方式
/*** 饿汉模式* 单例实例在类装载时进行创建*/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());}}
懒汉式单例模式
双重检查锁
错误示例
双重检查
第一次检查时候,没有锁定,看这个有没有被实例化,有就直接返回
第二次检查,表面是没有初始化才第二次,锁定,初始化,返回
public class Single {private static Single instance = null;private Single() {}public static Single getInstance() {if(instance == null) {synchronized(Single.class) {if(instance == null)instance = new Single();}}return instance;}}
我们先看 instance=new Single();
未被编译器优化的操作:
- 指令1:分配一款内存M
- 指令2:在内存M上初始化Singleton对象
- 指令3:将M的地址赋值给instance变量
编译器优化后的操作指令:
- 指令1:分配一块内存S
- 指令2:将M的地址赋值给instance变量
- 指令3:在内存M上初始化Singleton对象
现在有2个线程,刚好执行的代码被编译器优化过,过程如下:
正确示例
用volatile禁止重排序
/*** 懒汉模式 -》 双重同步锁单例模式* 单例实例在第一次使用时进行创建*/public class SingletonExample5 {// 私有构造函数private SingletonExample5() {}// 1、memory = allocate() 分配对象的内存空间// 2、ctorInstance() 初始化对象// 3、instance = memory 设置instance指向刚分配的内存// JVM和cpu优化,发生了指令重排// 1、memory = allocate() 分配对象的内存空间// 3、instance = memory 设置instance指向刚分配的内存// 2、ctorInstance() 初始化对象// 单例对象 volatile + 双重检测机制 -> 禁止指令重排private volatile static SingletonExample5 instance = null;// 静态的工厂方法public static SingletonExample5 getInstance() {if (instance == null) { // 双重检测机制 // Bsynchronized (SingletonExample5.class) { // 同步锁if (instance == null) {instance = new SingletonExample5(); // A - 3}}}return instance;}}
静态内部类
由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由static修饰,保证只被实例化一次,并且严格保证实例化顺序
public class SingletonObject6 {private SingletonObject6(){}// 单例持有者private static class InstanceHolder{private final static SingletonObject6 instance = new SingletonObject6();}public final static SingletonObject6 getInstance(){// 调用内部类属性return InstanceHolder.instance;}}
静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。
枚举模式
JVM 会阻止反射获取枚举类的私有构造方法
/*** 枚举模式:最安全*/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;}}}
破坏单例模式的方法及解决办法
1、除枚举方式外, 其他方法都会通过反射的方式破坏单例,反射是通过调用构造方法生成新的对象,所以如果我们想要阻止单例破坏,可以在构造方法中进行判断,若已有实例, 则阻止生成新的实例,解决办法如下:
private SingletonObject1() {if (instance != null) {throw new RuntimeException("实例已经存在,请通过 getInstance()方法获取");}}
2、如果单例类实现了序列化接口Serializable, 就可以通过反序列化破坏单例,所以我们可以不实现序列化接口,如果非得实现序列化接口,可以重写反序列化方法readResolve(), 反序列化时直接返回相关单例对象。
public Object readResolve() throws ObjectStreamException {return instance;}
序列化打破单例和解决
import java.io.Serializable;/*** 使用双重校验锁方式实现单例*/public class Singleton implements Serializable{private volatile static Singleton singleton;private Singleton (){}public static Singleton getSingleton() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}}
写一个序列化的测试类:
import java.io.*;public class SerializableDemo1 {//为了便于理解,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记//Exception直接抛出public static void main(String[] args) throws IOException, ClassNotFoundException {//Write Obj to fileObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));oos.writeObject(Singleton.getSingleton());//Read Obj from fileFile file = new File("tempFile");ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));Singleton newInstance = (Singleton) ois.readObject();//判断是否是同一个对象System.out.println(newInstance == Singleton.getSingleton());}}
输出结构为false,说明:
通过对Singleton的序列化与反序列化得到的对象是一个新的对象,这就破坏了Singleton的单例性
序列化破坏单例的过程
ObjectInputStream
对象的序列化过程通过ObjectOutputStream和ObjectInputputStream来实现的。为了节省篇幅,这里给出ObjectInputStream的readObject的调用栈:
重点代码,readOrdinaryObject方法的代码片段:
private Object readOrdinaryObject(boolean unshared)throws IOException{//此处省略部分代码Object obj;try {obj = desc.isInstantiable() ? desc.newInstance() : null;} catch (Exception ex) {throw (IOException) new InvalidClassException(desc.forClass().getName(),"unable to create instance").initCause(ex);}//此处省略部分代码if (obj != null &&handles.lookupException(passHandle) == null &&desc.hasReadResolveMethod()){Object rep = desc.invokeReadResolve(obj);if (unshared && rep.getClass().isArray()) {rep = cloneArray(rep);}if (rep != obj) {handles.setObject(passHandle, obj = rep);}}return obj;}
Object obj;try {obj = desc.isInstantiable() ? desc.newInstance() : null;} catch (Exception ex) {throw (IOException) new InvalidClassException(desc.forClass().getName(),"unable to create instance").initCause(ex);}
这里创建的这个obj对象,就是本方法要返回的对象,也可以暂时理解为是ObjectInputStream的readObject返回的对象。
isInstantiable:如果一个serializable/externalizable的类可以在运行时被实例化,那么该方法就返回true。针对serializable和externalizable我会在其他文章中介绍。desc.newInstance:该方法通过反射的方式调用无参构造方法新建一个对象。
这就解释了原因,为什么序列化会破坏单例?:序列化会通过反射调用无参数的构造方法创建一个新的对象。
防止序列化破坏单例模式
先给出解决方案,然后再具体分析原理:
只要在Singleton类中定义readResolve就可以解决该问题:
import java.io.Serializable;/*** 使用双重校验锁方式实现单例*/public class Singleton implements Serializable{private volatile static Singleton singleton;private Singleton (){}public static Singleton getSingleton() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}private Object readResolve() {return singleton;}}
还是运行上面测试类:
本次输出结果为true。具体原理,我们回过头继续分析以下ObjectInputStream的readObject的调用代码:
if (obj != null &&handles.lookupException(passHandle) == null &&desc.hasReadResolveMethod()){Object rep = desc.invokeReadResolve(obj);if (unshared && rep.getClass().isArray()) {rep = cloneArray(rep);}if (rep != obj) {handles.setObject(passHandle, obj = rep);}}
hasReadResolveMethod:如果实现了serializable 或者 externalizable接口的类中包含readResolve则返回trueinvokeReadResolve:通过反射的方式调用要被反序列化的类的readResolve方法。
所以,原理也就清楚了,主要在Singleton中定义readResolve方法,并在该方法中指定要返回的对象的生成策略,就可以防止单例被破坏。
