单例模式分析

编程

单例模式,这个搞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

回到顶部