vlambda博客
学习文章列表

《java大法好》之 单例模式

·今天给大家分享一下23种设计模式中的单例模式及如何破解单例模式

什么是单例模式呢,顾名思义,单例模式就是在一个程序中某一个对象只存在且最多存在一个实例

而单例模式的实现方式又可以细分为 懒汉式、饿汉式、双重检测锁模式、静态内部类模式和枚举式,下面来给大家详细介绍一下


单例模式介绍


·懒汉式

优点:线程安全,效率高

缺点:不能延时加载

package pattern23.singleton;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class Singleton01{ Logger logger = LoggerFactory.getLogger(this.getClass());private static final Singleton01 instance = new Singleton01();private Singleton01() {}public static Singleton01 getInstance() {return instance; }public void doSomething(){ logger.info("hello,[{}]", instance); }public static void main(String[] args) { Singleton01 singleton01 = Singleton01.getInstance(); Singleton01 singleton02 = Singleton01.getInstance(); singleton01.doSomething(); singleton02.doSomething(); }}

输出结果:

打印结果为同一对象,懒汉式测试通过




 ·饿汉式

优点:线程安全,可以延迟加载

缺点:效率不高

package pattern23.singleton;
import org.slf4j.Logger;import org.slf4j.LoggerFactory;
public class Singleton02 { Logger logger = LoggerFactory.getLogger(this.getClass()); private static Singleton02 instance; private Singleton02() {}
public synchronized static Singleton02 getInstance() { if(instance == null){ instance = new Singleton02(); } return instance; } public void doSomething(){ logger.info("hello,[{}]", instance); } public static void main(String[] args) { Singleton02 singleton01 = Singleton02.getInstance(); Singleton02 singleton02 = Singleton02.getInstance(); singleton01.doSomething(); singleton02.doSomething(); }}

输出结果:

《java大法好》之 单例模式

打印结果为同一对象,饿汉式测试通过



· 双重检测锁模式 (由于jvm底层模型原因,可能会出问题,不建议使用)

缺点:由于jvm底层模型原因,可能会出问题


package pattern23.singleton;
import org.slf4j.Logger;import org.slf4j.LoggerFactory;
public class Singleton03 {    Logger logger = LoggerFactory.getLogger(this.getClass()); private static Singleton03 instance;
private Singleton03() {}
public static Singleton03 getInstance() { if(instance == null){ Singleton03 singleton03; synchronized (Singleton03.class){ singleton03 = instance; if(singleton03 == null){ synchronized (Singleton03.class){ if(singleton03 == null){ singleton03 = new Singleton03(); } } instance = singleton03; } } } return instance; } public void doSomething(){ logger.info("hello,[{}]", instance); }
public static void main(String[] args) { Singleton03 singleton01 = Singleton03.getInstance(); Singleton03 singleton02 = Singleton03.getInstance(); singleton01.doSomething(); singleton02.doSomething(); }}

输出结果:

《java大法好》之 单例模式

打印结果为同一对象,双重检测锁模式测试通过(不推荐使用这种方式实现)



·静态内部类模式

优点:线程安全,效率高,可以延时加载


package pattern23.singleton;
import org.slf4j.Logger;import org.slf4j.LoggerFactory;
public class Singleton04 { Logger logger = LoggerFactory.getLogger(this.getClass()); private static class SingletonInstance { private static final Singleton04 instance = new Singleton04(); } public static Singleton04 getInstance(){ return SingletonInstance.instance; } private Singleton04(){} public void doSomething(){ logger.info("hello,[{}]", SingletonInstance.instance); } public static void main(String[] args) { Singleton04 singleton01 = Singleton04.getInstance(); Singleton04 singleton02 = Singleton04.getInstance(); singleton01.doSomething(); singleton02.doSomething(); }}

输出结果:

《java大法好》之 单例模式

打印结果为同一对象,静态内部类模式测试通过



·静态内部类模式

优点:线程安全,效率高,可以延时加载

缺点:不能延时加载


package pattern23.singleton;
import org.slf4j.Logger;import org.slf4j.LoggerFactory;
public enum Singleton05 { INSTANCE; Logger logger = LoggerFactory.getLogger(this.getClass());
public void doSomething() { logger.info("hello,[{}]", INSTANCE); }
    public static void main(String[] args) { Singleton05 singleton01 = Singleton05.INSTANCE; Singleton05 singleton02 = Singleton05.INSTANCE; singleton01.doSomething(); singleton02.doSomething(); }}


输出结果:

《java大法好》之 单例模式

枚举类型由JVM优化,天然单例。枚举方式测试通过



单例模式的破解

·使用反射破解单例模式

话不多说直接上代码

package pattern23.singleton;

import org.slf4j.Logger;import org.slf4j.LoggerFactory;
import java.lang.reflect.Constructor;
public class Client02 { static Logger logger = LoggerFactory.getLogger(Client02.class);
public static void main(String[] args) throws Exception {     // 使用正常方式获取一个实例 Singleton01 singleton01_1 = Singleton01.getInstance();        // 使用正常方式再获取一个实例 Singleton01 singleton01_2 = Singleton01.getInstance(); logger.info("Hello:{}", singleton01_1); logger.info("Hello:{}", singleton01_2); // 使用反射获取一个新的实例        reflectDemo(); }
public static void reflectDemo() throws Exception { // 通过反射加载类 Class<Singleton01> clazz = (Class<Singleton01>) Class.forName("pattern23.singleton.Singleton01");        // 获取类的无参构造方法(单例模式中一般将构造方法声明为private) Constructor<Singleton01> constructors = clazz.getDeclaredConstructor();        // 打开私有方法的执行权限 constructors.setAccessible(true);        // 使用构造方法获取新的实例 Singleton01 singleton01_3 = constructors.newInstance(); logger.info("Hello:{}", singleton01_3);    }}

执行结果:

《java大法好》之 单例模式

从控制台输出信息可以看到,前两次的打印都是同一个实例,而第三次打印就是一个新的实例。此时单例模式便被破解了


那么如何防止单例模式被破解呢,很简单,只需要在构造方法里判断一次实例是否为null即可,上代码:

package pattern23.singleton;
import org.slf4j.Logger;import org.slf4j.LoggerFactory;
public class Singleton01 { Logger logger = LoggerFactory.getLogger(this.getClass()); private static final Singleton01 instance = new Singleton01();
private Singleton01() {    // 在构造方法里判断一下实例是否为null    // 如果为null则正常执行    // 如果不为null则说明是有人想使用反射来破解我们的单例模式 if (instance != null) { throw new RuntimeException("禁止通过反射创建多个实例"); } }
public static Singleton01 getInstance() { return instance; }
public void doSomething() { logger.info("hello,[{}]", instance);    }}

好了,下面我们来测试一下,来看一下修改了代码之后的执行结果:


《java大法好》之 单例模式

可以看见,前两次正常获取的实例皆为同一个对象,而第三次我们使用反射想要获取一个新的实例时就抛出了异常。自此,防止使用反射破解单例模式便改造完成了。



·使用反序列化解单例模式

除了使用反射破解单例模式外还可以使用反序列化来破解单例模式,下面给大家演示一下如何使用反序列化来破解单例模式:

package pattern23.singleton;

import org.slf4j.Logger;import org.slf4j.LoggerFactory;
import java.io.*;
public class Client02 { static Logger logger = LoggerFactory.getLogger(Client02.class);
public static void main(String[] args) throws Exception {        Singleton06 singleton06_1 = Singleton06.getInstance(); logger.info("Hello:{}", singleton06_1); File file = new File("SingletonDemoFile.temp"); FileOutputStream fileOutputStream = new FileOutputStream(file); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(singleton06_1);
FileInputStream fileInputStream = new FileInputStream(file); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); Singleton06 singleton06_2 = (Singleton06) objectInputStream.readObject(); logger.info("Hello:{}", singleton06_2); Singleton06 singleton06_3 = Singleton06.getInstance(); logger.info("Hello:{}", singleton06_3);    }}

下面我们执行一下这段代码,控制台打印如下:

《java大法好》之 单例模式

可以看见第一次打印和第三次打印的都是同一个实例,而第二次打印的则是一个新的实例。怎么肥事,单例模式又被破解了......


那么我们应该怎么防止怪蜀黍用反序列化的方式来破解单例模式呢,这是一个值得思考的问题,爱折腾的小编在Serializable接口中发现了这样一句话

《java大法好》之 单例模式

可以在readResolve()方法中使用任意对象来替换反序列化得到的对象,好像有了一些思路....

下面上代码:

package pattern23.singleton;
import org.slf4j.Logger;import org.slf4j.LoggerFactory;
import java.io.ObjectStreamException;import java.io.Serializable;
public class Singleton06 implements Serializable { Logger logger = LoggerFactory.getLogger(this.getClass());    private static final Singleton06 instance = new Singleton06();
public static Singleton06 getInstance() { return instance; }
public void doSomething() { logger.info("hello,[{}]", instance); }
    // 这里直接返回当前实例对象 private Object readResolve() throws ObjectStreamException { return instance; }}

我们再来测试一下改造之后的代码:

这次我们可以看见,三次打印的都是同一个实例,自此,使用反序列化破解单例模式这条路也被我们堵住了。







小白程序员学习分享,有可以改进的地方还希望大家多多提出建议共同成长