1.单例模式

目录

简介

饿汉式

懒汉式

双重检测锁式

静态内部类式

枚举单例

测试

测试单例模式:

测试五种单例模式在多线程环境下的效率

问题(拓展)

例:反射破解单例模式

例:反序列化破解单例模式

总结:如何用


简介

单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个 JVM 中,该对象只有一个实例存在。这样的模式有几个好处:

单例模式的优点:

  • 由于单例模式只生产一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决
  • 单例模式可以在系统设置全局的访问点,优化环境共享资源访问,例如可以设计一个单例类,负责所以数据表的映射处理

常见的五种单例模式实现方式

  • 主要:
    • 饿汉式(线程安全,调用效率高;但是,不能延迟加载)
    • 懒汉式(线程安全,调用效率不高;但是,可以延迟加载)
  • 其他:
    • 双重检测锁式(由于 JVM 底层内部模型原因,偶尔会出问题,不建议使用)
    • 静态内部类式(线程安全,调用效率高;但是,可以延时加载)
    • 枚举单例(线程安全,调用效率高,不能延时加载)

饿汉式

java 复制代码
/**
 * @ProjectName:
 * @Package:        com.example.gof23.creational_patterns.singleton
 * @ClassName:      SingletonHg
 * @Description:    饿汉式单例模式(Hungry)
 * @Author:         qfxl
 * @CreateDate:     2024/09/09 23:00
 * @Version:        1.0.0
 */
public class SingletonHungry {

    //类初始化时,立即加载这个对象
    //(在类加载器加载时,是天然的线程安全模式;同时因为是立即加载,所以没有延迟加载的优势)
    private static SingletonHungry instance = new SingletonHungry();

    //私有化构造器
    private SingletonHungry() {
    }

    //方法没有同步,调用效率高
    public static SingletonHungry getInstance() {
        return instance;
    }
}

懒汉式

java 复制代码
/**
 * @ProjectName:
 * @Package:        com.example.gof23.creational_patterns.singleton
 * @ClassName:      SingletonLazy
 * @Description:    懒汉式单例模式(Lazy)
 * @Author:         qfxl
 * @CreateDate:     2024/09/09 23:00
 * @Version:        1.0.0
 */
public class SingletonLazy {

    //类初始化时,不初始化这个对象(实现懒加载 或者叫 延迟加载(lazy load),真正用到的时候才加载)
    private static SingletonLazy instance;

    //私有化构造器
    private SingletonLazy() {
    }

    //方法同步,调用效率低
    public static synchronized SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }
}

双重检测锁式

java 复制代码
/**
 * @ProjectName:
 * @Package:        com.example.gof23.creational_patterns.singleton
 * @ClassName:      SingletonDC
 * @Description:    双重检测锁式单例模式(Double Checked Locking)
 * @Author:         qfxl
 * @CreateDate:     2024/09/09 23:10
 * @Version:        1.0.0
 */
public class SingletonDC {

    //使用了volatile关键字后,重排序被禁止,所有的写(write)操作都将发生在读(read)操作之前
    private volatile static SingletonDC instance;

    //私有化构造器
    private SingletonDC() {
    }

    //双重检测锁式
    public SingletonDC getInstance() {
        if (instance == null) {
            synchronized (SingletonDC.class) {
                if (instance == null) {
                    instance = new SingletonDC();
                }
            }
        }
        return instance;
    }
}

静态内部类式

java 复制代码
/**
 * @ProjectName:
 * @Package:        com.example.gof23.creational_patterns.singleton
 * @ClassName:      SingletonSIC
 * @Description:    静态内部类式单例模式(Static Inner Class)
 * @Author:         qfxl
 * @CreateDate:     2024/09/09 23:10
 * @Version:        1.0.0
 */
public class SingletonSIC {

    private static class SingletonClassInstance {
        private static final SingletonSIC instance = new SingletonSIC();
    }

    //私有化构造器
    private SingletonSIC() {
    }

    public static SingletonSIC getInstance(){
        return SingletonClassInstance.instance;
    }
}

枚举单例

java 复制代码
/**
 * @ProjectName:
 * @Package:        com.example.gof23.creational_patterns.singleton
 * @ClassName:      SingletonEnum
 * @Description:    枚举类单例模式(Enum)
 * @Author:         qfxl
 * @CreateDate:     2024/09/09 23:10
 * @Version:        1.0.0
 */
public enum SingletonEnum {

    //这个枚举元素,本身就是单例对象(没有延时加载)
    INSTANCE;

    //添加自己需要的操作
    public void singletonOperation() {

    }
}

测试

测试单例模式:

java 复制代码
public class Client {
    public static void main(String[] args) {
        //测试饿汉式单例模式(Hungry)
        SingletonHungry hungry1 = SingletonHungry.getInstance();
        SingletonHungry hungry2 = SingletonHungry.getInstance();
        System.out.println(hungry1);
        System.out.println(hungry2);

        //测试懒汉式单例模式
        SingletonLazy lazy1 = SingletonLazy.getInstance();
        SingletonLazy lazy2 = SingletonLazy.getInstance();
        System.out.println(lazy1);
        System.out.println(lazy2);

        //测试双重检测锁单例模式
        SingletonDC dc1 = SingletonDC.getInstance();
        SingletonDC dc2 = SingletonDC.getInstance();
        System.out.println(dc1);
        System.out.println(dc2);

        //测试静态内部类式单例模式
        SingletonSIC sic1 = SingletonSIC.getInstance();
        SingletonSIC sic2 = SingletonSIC.getInstance();
        System.out.println(sic1);
        System.out.println(sic2);

        //测试枚举单例模式
        SingletonEnum anEnum1 = SingletonEnum.INSTANCE;
        SingletonEnum anEnum2 = SingletonEnum.INSTANCE;
        System.out.println(anEnum1==anEnum2);
    }
}

结果为:

bash 复制代码
饿汉式:com.example.gof23.creational_patterns.singleton.SingletonHungry@1540e19d
饿汉式:com.example.gof23.creational_patterns.singleton.SingletonHungry@1540e19d
懒汉式:com.example.gof23.creational_patterns.singleton.SingletonLazy@677327b6
懒汉式:com.example.gof23.creational_patterns.singleton.SingletonLazy@677327b6
双重检测锁:com.example.gof23.creational_patterns.singleton.SingletonDC@14ae5a5
双重检测锁:com.example.gof23.creational_patterns.singleton.SingletonDC@14ae5a5
静态内部类:com.example.gof23.creational_patterns.singleton.SingletonSIC@7f31245a
静态内部类:com.example.gof23.creational_patterns.singleton.SingletonSIC@7f31245a
枚举单例:true

测试五种单例模式在多线程环境下的效率

(关注相对值即可,不同的环境下测试值完全不一样)

五种单例模式 时间
饿汉式(SingletonHungry) 26ms
懒汉式(SingletonLazy) 186ms
双重检测锁式(SingletonDC) 40ms
静态内部类式(SingletonSIC) 31ms
枚举单例(SingletonEnum) 37ms
  • CountDownLatch
    • 同步辅助类,在完全一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待
    • countDown():当前线程调用此方法,则计数减一(建议放在 finally 里执行)
    • await():调用此方法会一直阻塞当前线程,直到计时器的值为0
java 复制代码
public class ClientTimes {
    public static void main(String[] args) throws InterruptedException {

        long start = System.currentTimeMillis();
        int threadNum = 10;
        final CountDownLatch countDownLatch = new CountDownLatch(threadNum);

        for (int i = 0; i < threadNum; i++) {
            new Thread(new Runnable() {
                public void run() {
                    for (int i = 0; i < 1000000; i++) {
                        //分别对下面的单例模式进行测试
                        SingletonHungry hungry = SingletonHungry.getInstance();
//                        SingletonLazy lazy = SingletonLazy.getInstance();
//                        SingletonDC dc = SingletonDC.getInstance();
//                        SingletonSIC sic = SingletonSIC.getInstance();
//                        SingletonEnum anEnum = SingletonEnum.INSTANCE;
                    }
                    countDownLatch.countDown();
                }
            }).start();
        }

        //main线程阻塞,直到计数器变为0,才会继续执行
        countDownLatch.await();
        long end = System.currentTimeMillis();
        System.out.println("总耗时:" + (end - start));
    }
}

测试结果如上表格

问题(拓展)

  • 反射可以破解上面几种(不包含枚举式)实现反式(可以在构造方法中手动抛出异常控制)
  • 反序列化可以破解上面几种(不包含枚举式)实现方式(可以通过定义 readResolver() 防止获得不同的对象)
    • 反序列化时,如果对象所在的类定义了 readResolver(),(实际是一种回调),定义返回那个对象

例:反射破解单例模式

java 复制代码
package com.example.gof23.creational_patterns.singleton.expand;

public class SingletonDemo {

    private static SingletonDemo instance = new SingletonDemo();

    //私有化构造器
    private SingletonDemo() {
    }

    public static SingletonDemo getInstance() {
        return instance;
    }

}
java 复制代码
public class Test_reflect {

    public static void main(String[] args) throws Exception {

        //通过反射来破解单例模式(通过反射的方式直接调用私有化构造器)
        Class<SingletonDemo> clazz = (Class<SingletonDemo>) Class.forName("com.example.gof23.creational_patterns.singleton.expand.SingletonDemo");
        Constructor<SingletonDemo> c = clazz.getDeclaredConstructor(null);
        c.setAccessible(true);//跳过权限的检测,使其可以访问私有的方法
        SingletonDemo sd1 = c.newInstance();
        SingletonDemo sd2 = c.newInstance();

        System.out.println(sd1);
        System.out.println(sd2);

    }
}

结果为:

bash 复制代码
com.example.gof23.creational_patterns.singleton.expand.SingletonDemo@1540e19d
com.example.gof23.creational_patterns.singleton.expand.SingletonDemo@677327b6

防止反射破解单例模式:在构造方法中手动抛出异常控制

java 复制代码
package com.example.gof23.creational_patterns.singleton.expand;

public class SingletonDemo {

    private static SingletonDemo instance = new SingletonDemo();

    //私有化构造器
    private SingletonDemo() {
        //防止反射获取私有化的构造方法,从而破解单例模式
        if (instance != null) {
            throw new RuntimeException();
        }
    }

    public static SingletonDemo getInstance() {
        return instance;
    }

}

再运行的结果为:

bash 复制代码
com.example.gof23.creational_patterns.singleton.expand.SingletonDemo@135fbaa4
com.example.gof23.creational_patterns.singleton.expand.SingletonDemo@135fbaa4

例:反序列化破解单例模式

java 复制代码
package com.example.gof23.creational_patterns.singleton.expand;

import java.io.Serializable;

public class SingletonDemo implements Serializable {

    private static SingletonDemo instance = new SingletonDemo();

    //私有化构造器
    private SingletonDemo() {
    }

    public static SingletonDemo getInstance() {
        return instance;
    }

}
java 复制代码
public class Test_serializable {

    public static void main(String[] args) throws Exception {

        //通过反序列化的方式构造多个对象
        SingletonDemo instance1 = SingletonDemo.getInstance();

        FileOutputStream fos = new FileOutputStream("e:/a.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(instance1);

        FileInputStream fis = new FileInputStream("e:/a.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);
        SingletonDemo instance2 = (SingletonDemo) ois.readObject();

        System.out.println(instance1);
        System.out.println(instance2);
    }
}

结果为:

bash 复制代码
com.example.gof23.creational_patterns.singleton.expand.SingletonDemo@135fbaa4
com.example.gof23.creational_patterns.singleton.expand.SingletonDemo@58372a00

防止反序列化破解单例模式:通过定义 readResolver() 防止获得不同的对象

java 复制代码
package com.example.gof23.creational_patterns.singleton.expand;

import java.io.Serializable;

public class SingletonDemo implements Serializable {

    private static SingletonDemo instance = new SingletonDemo();

    //私有化构造器
    private SingletonDemo() {
        //防止反射获取私有化的构造方法,从而破解单例模式
        if (instance != null) {
            throw new RuntimeException();
        }
    }

    public static SingletonDemo getInstance() {
        return instance;
    }

    //在反序列化时,如果定义了此方法,则直接返回此方法中的对象,无需单独再创建新对象
    private Object readResolve() {
        return instance;
    }

}

再运行结果为:

bash 复制代码
com.example.gof23.creational_patterns.singleton.expand.SingletonDemo@135fbaa4
com.example.gof23.creational_patterns.singleton.expand.SingletonDemo@135fbaa4

总结:如何用

  • 单例对象占用资源少,不需要延迟加载:
    • 枚举式 好于 饿汉式
  • 单例对象占用资源大,需要延迟加载:
    • 静态内部类式 好于 懒汉式
相关推荐
无尽的大道9 小时前
单例模式详解:如何优雅地实现线程安全的单例
单例模式
Hello.Reader18 小时前
单例模式全面解析
单例模式
编程修仙1 天前
java的单例设计模式
java·单例模式·设计模式
L_cl2 天前
Python学习从0到1 day27 Python 高阶技巧 ③ 设计模式 — 单例模式
学习·单例模式·设计模式
ktkiko114 天前
Java中的设计模式——单例模式、代理模式、适配器模式
java·单例模式·设计模式
傻傻虎虎4 天前
【真题笔记】21年系统架构设计师案例理论点总结
单例模式·系统架构·uml·命令模式
Mr. zhihao5 天前
享元模式及其运用场景:结合工厂模式和单例模式优化内存使用
单例模式·享元模式
南城花随雪。5 天前
Spring框架之单例模式 (Singleton Pattern)
java·spring·单例模式
编程、小哥哥5 天前
设计模式之单列模式(7种单例模式案例,Effective Java 作者推荐枚举单例模式)
java·单例模式·设计模式
·Lntano·远方5 天前
线程安全的单例模式
java·开发语言·单例模式