单例模式分析
单例模式,这个搞java的应该都懂,对于那些有缺陷的单例模式就不说了,太low,这里说下相对略叼的两种。
一、双重校验锁
先上代码:
package com.ayo.singleton;import java.io.*;
/**
* 双重校验锁单例模式
*
* @Authror ayo
* @Date 2020/1/7 14:33
*/
public class LanhanSingleton implements Serializable {
private volatile static LanhanSingleton INSTANCE;
private LanhanSingleton() {}
public static LanhanSingleton getInstance(){
if(INSTANCE == null){
synchronized (LanhanSingleton.class){
if (INSTANCE == null){
INSTANCE = new LanhanSingleton();
}
}
}
return INSTANCE;
}
}
双重校验锁基本上算是比较完善的单例模式了(大部分场景下),但是它还是有缺点的,因为如果按照这么写并不能真正的保证单例,最简单的就是用反射搞你:
package com.ayo.singleton;import java.io.*;
import java.lang.reflect.Constructor;
/**
* 双重校验锁单例模式
*
* @Authror ayo
* @Date 2020/1/7 14:33
*/
public class LanhanSingleton implements Serializable {
private volatile static LanhanSingleton INSTANCE;
private LanhanSingleton() {}
public static LanhanSingleton getInstance(){
if(INSTANCE == null){
synchronized (LanhanSingleton.class){
if (INSTANCE == null){
INSTANCE = new LanhanSingleton();
}
}
}
return INSTANCE;
}
public static void main(String[] args) throws Exception {
/**
* 反射获取对象
*/
LanhanSingleton instance = LanhanSingleton.getInstance();
//获取类的构造器
Constructor<LanhanSingleton> lanhanSingletonConstructor = LanhanSingleton.class.getDeclaredConstructor();
lanhanSingletonConstructor.setAccessible(true);
LanhanSingleton lanhanSingleton1 = lanhanSingletonConstructor.newInstance();
System.out.println("通过反射获取到的对象和单例对象是否相同? = " + (instance == lanhanSingleton1));
}
}
这个很好防止,因为类是你自己定义的嘛
package com.ayo.singleton;import java.io.*;
import java.lang.reflect.Constructor;
/**
* 双重校验锁单例模式
*
* @Authror ayo
* @Date 2020/1/7 14:33
*/
public class LanhanSingleton implements Serializable {
private volatile static LanhanSingleton INSTANCE;
private LanhanSingleton() {
//阻止反射
if (INSTANCE != null && this != INSTANCE){
throw new SecurityException("反射你妹啊! ");
}
}
public static LanhanSingleton getInstance(){
if(INSTANCE == null){
synchronized (LanhanSingleton.class){
if (INSTANCE == null){
INSTANCE = new LanhanSingleton();
}
}
}
return INSTANCE;
}
public static void main(String[] args) throws Exception {
LanhanSingleton instance = LanhanSingleton.getInstance();
/**
* 反射获取对象
*/
//获取类的构造器
Constructor<LanhanSingleton> lanhanSingletonConstructor = LanhanSingleton.class.getDeclaredConstructor();
lanhanSingletonConstructor.setAccessible(true);
LanhanSingleton lanhanSingleton1 = lanhanSingletonConstructor.newInstance();
System.out.println("通过反射获取到的对象和单例对象是否相同? = " + (instance == lanhanSingleton1));
}
}
反射很容易禁止,但是还有一种方法,就是反序列化,这种方式也可以生成一个新的对象,看下
package com.ayo.singleton;import java.io.*;
/**
* 双重校验锁单例模式
*
* @Authror ayo
* @Date 2020/1/7 14:33
*/
public class LanhanSingleton implements Serializable {
private volatile static LanhanSingleton INSTANCE;
private LanhanSingleton() {
//阻止反射
if (INSTANCE != null && this != INSTANCE){
throw new SecurityException("反射你妹啊! ");
}
}
public static LanhanSingleton getInstance(){
if(INSTANCE == null){
synchronized (LanhanSingleton.class){
if (INSTANCE == null){
INSTANCE = new LanhanSingleton();
}
}
}
return INSTANCE;
}
public static void main(String[] args) throws Exception {
/**
* 序列化
*/
//获得输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("lanhanFile"));
//将单例对象序列化到磁盘
oos.writeObject(LanhanSingleton.getInstance());
//获取输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("lanhanFile"));
//从磁盘读取被序列化的单例对象
LanhanSingleton lanhanSingleton = (LanhanSingleton) ois.readObject();
//判断两者是否相等
System.out.println("反序列化后的对象和原对象是否相等? = " + (lanhanSingleton == LanhanSingleton.getInstance()));
}
}
感觉是不是有点懵比,为什么反序列化后对象变了,跟进去看看(打上你的断点debug)
跟源码的时候会看到主要经过了以下几个方法:
ok,当你跟到这里时发现调用了反射(利用了ObjectStreamClass中的构造器,所以是不是你在构造器中加校验没啥卵用?不是的,这种构造器程序员调不了的,但是你禁止不了,因为反序列化时会调啊,一样会出问题,这怎么办?),我知道你又懵逼了,别着急,继续往下看源码
仔细调试下这段逻辑,都打上断点,最后你会发现他进入了hasReadResolveMethod方法,
从而导致表达式desc.hasReadResolveMethod()为false,继续分析发现这个readResolveMethod其实是反射获取desc(com.ayo.singleton.LanhanSingleton: static final long serialVersionUID = -6555766677206059999L;)中的readResolve方法,如果这个方法不为空,返回的对象其实是这个方法的返回值,增加一个方法
private Object readResolve(){ return INSTANCE;
}
搞定了。
二、枚举
直接上代码了(累了,看源码真的看的眼疼)
package com.ayo.singleton;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
/**
* 基于枚举的单例模式
*
* @Authror ayo
* @Date 2020/1/7 14:53
*/
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
public static void main(String[] args) throws Exception {
/**
* 序列化
*/
//获得输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("enumFile"));
//将单例对象序列化到磁盘
oos.writeObject(INSTANCE);
//获取输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("enumFile"));
//从磁盘读取被序列化的单例对象
EnumSingleton enumSingleton = (EnumSingleton) ois.readObject();
//判断两者是否相等
System.out.println("反序列化后的对象和原对象是否相等? = " + (INSTANCE == enumSingleton));
Thread.sleep(1000);
/**
* 反射获取对象
*/
//获取类的构造器
Constructor<EnumSingleton> enumSingletonConstructor = EnumSingleton.class.getDeclaredConstructor();
enumSingletonConstructor.setAccessible(true);
EnumSingleton enumSingletonReflect = enumSingletonConstructor.newInstance();
System.out.println("通过反射获取到的对象和单例对象是否相同? = " + (INSTANCE == enumSingletonReflect));
}
}
这时候你会发现枚举单例的好处了,两种问题都避免了,为什么?
先说反序列化,
之前的步骤和普通对象都一样,但是这一步你会发现枚举走的是TC_ENUM,分析readEnum方法,看到valueof方法了吧
然后是反射,
这更简单了,其实就是不准反射枚举呗,通过源码分析可以知道其实枚举类是被特殊对待了,所以天然的支持单例模式。
以上是 单例模式分析 的全部内容, 来源链接: utcz.com/z/512482.html