Singleton单例设计模式详解

目录

模式定义

保证一个类只有一个实例,并且提供一个全局访问点

应用场景

重量级的对象,不需要多个实例,如线程池,数据库连接池

实现方式

1.懒汉模式:

延迟加载 ,只有在真正使用的时候,才开始实例化。

1)线程安全问题。

2)double check 加锁优化。

3)编译器(JIT),CPU 有可能对指令进行重排序,导致使用到尚未初始化的实例,可以通过添加volatile 关键字进行修饰,对于volatile 修饰的字段,可以防止指令重排。

java 复制代码
public class LazySingletonTest {
    public static void main(String[] args) {
//        LazySingleton instance1 = LazySingleton.getInstance();
//        LazySingleton instance2 = LazySingleton.getInstance();
//        System.out.println(instance1==instance2); //true
        new Thread(() -> {
            LazySingleton instance1 = LazySingleton.getInstance();
            System.out.println(instance1);
        }).start();

        new Thread(() -> {
            LazySingleton instance2 = LazySingleton.getInstance();
            System.out.println(instance2);
        }).start();

    }
}

class LazySingleton {
    private volatile static LazySingleton instance;
    private LazySingleton() {
    }
    public static LazySingleton getInstance() {
        if (instance == null) {
            synchronized (LazySingleton.class){
                if (instance == null) {
                    instance = new LazySingleton();
                    // 字节码层
                    // JIT , CPU 有可能对如下指令进行重排序
                    // 1. 分配空间 2. 初始化 3. 引用赋值
                }
            }
        }
        return instance;
    }
}

2.饿汉模式:

类加载的初始化阶段就完成了实例的初始化 。

本质上就是借助于jvm类加载机制 ,保证实例的唯一性(初始化过程只会执行一次)及线程安全(JVM以同步的形式来完成类加载的整个过程)。

类加载过程:

  1. 加载二进制数据到内存中, 生成对应的Class数据结构,
  2. 连接: a. 验证, b.准备(给类的静态成员变量赋默认值),c.解析
  3. 初始化: 给类的静态变量赋初值
    只有在真正使用对应的类时,才会触发初始化 如( 当前类是启动类即main函数所在类,直接进行new 操作,访问静态属性、访问静态方法,用反射访问类,初始化一个类的子类等.)
java 复制代码
public class HungrySingletonTest {
    public static void main(String[] args) {
        HungrySingleton instance = HungrySingleton.getInstance();
        HungrySingleton instance1 = HungrySingleton.getInstance();
        System.out.println(instance==instance1);
    }
}
class HungrySingleton {
    private static HungrySingleton instance=new HungrySingleton();
    private HungrySingleton(){
    }
    public static HungrySingleton getInstance(){
        return instance;
    }
}

3.静态内部类

1).本质上是利用类的加载机制 来保证线程安全

2).只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一种形式。

java 复制代码
public class InnerClassSingletonTest {
    public static void main(String[] args) {
//        InnerClassSingleton instance = InnerClassSingleton.getInstance();
//        InnerClassSingleton instance1 = InnerClassSingleton.getInstance();
//        System.out.println(instance==instance1); //true

        new Thread(() -> {
            InnerClassSingleton instance = InnerClassSingleton.getInstance();
            System.out.println(instance);
        }).start();

        new Thread(() -> {
            InnerClassSingleton instance1 = InnerClassSingleton.getInstance();
            System.out.println(instance1);
        }).start();
    }
}
class InnerClassSingleton{
    private static class InnerClassHolder{
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){
    }
    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }
}

反射

java 复制代码
public class InnerClassSingletonTest {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//        InnerClassSingleton instance = InnerClassSingleton.getInstance();
//        InnerClassSingleton instance1 = InnerClassSingleton.getInstance();
//        System.out.println(instance==instance1); //true

//        new Thread(() -> {
//            InnerClassSingleton instance = InnerClassSingleton.getInstance();
//            System.out.println(instance);
//        }).start();
//
//        new Thread(() -> {
//            InnerClassSingleton instance1 = InnerClassSingleton.getInstance();
//            System.out.println(instance1);
//        }).start();

        Constructor<InnerClassSingleton> declaredConstructor = InnerClassSingleton.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance();
        InnerClassSingleton instance = InnerClassSingleton.getInstance();
        System.out.println(innerClassSingleton==instance); //false
    }
}
class InnerClassSingleton{
    private static class InnerClassHolder{
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){
    }
    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }
}

如何防止反射攻击破坏?

饿汉模式下无法避免,懒汉模式可在构造函数中增加如下代码,判断实例是否存在。

java 复制代码
    private InnerClassSingleton(){
        if (InnerClassHolder.instance!=null){
            throw new RuntimeException("单例不允许多个实例");
        }
    }
java 复制代码
public class InnerClassSingletonTest {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//        InnerClassSingleton instance = InnerClassSingleton.getInstance();
//        InnerClassSingleton instance1 = InnerClassSingleton.getInstance();
//        System.out.println(instance==instance1); //true

//        new Thread(() -> {
//            InnerClassSingleton instance = InnerClassSingleton.getInstance();
//            System.out.println(instance);
//        }).start();
//
//        new Thread(() -> {
//            InnerClassSingleton instance1 = InnerClassSingleton.getInstance();
//            System.out.println(instance1);
//        }).start();

        Constructor<InnerClassSingleton> declaredConstructor = InnerClassSingleton.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance();
        InnerClassSingleton instance = InnerClassSingleton.getInstance();
        System.out.println(innerClassSingleton==instance); //false
    }
}
class InnerClassSingleton{
    private static class InnerClassHolder{
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){
        if (InnerClassHolder.instance!=null){
            throw new RuntimeException("单例不允许多个实例");
        }
    }
    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }
}

枚举类型

1)天然不支持反射创建对应的实例 ,且有自己的反序列化机制

2)利用类加载机制保证线程安全

java 复制代码
public enum EnumSingletonTest {
    INSTANCE;

    private void print(){
        System.out.println(this.hashCode());
    }
}

class EnumTest{
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException, ClassNotFoundException {
        EnumSingletonTest instance = EnumSingletonTest.INSTANCE;
//        EnumSingletonTest instance1 = EnumSingletonTest.INSTANCE;
//        System.out.println(instance==instance1); // true

//        Constructor<EnumSingletonTest> declaredConstructor = EnumSingletonTest.class.getDeclaredConstructor(String.class,int.class);
//        declaredConstructor.setAccessible(true);
//        EnumSingletonTest instance = declaredConstructor.newInstance("INSTANCE",0);

//        ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("enumsingleton"));
//        oss.writeObject(instance);
//        oss.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("enumsingleton"));
        EnumSingletonTest object = ((EnumSingletonTest) ois.readObject());
        System.out.println(instance==object); //true:有自己的反序列化机制
    }
}

序列化

可以利用指定方法readResolve来替换从反序列化流中的数据 如下

java 复制代码
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
ANY‐ACCESS‐MODIFIER Object readResolve() throws ObjectStreamException;
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
java 复制代码
public class InnerClassSingletonTest {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException, ClassNotFoundException {
//        InnerClassSingleton instance = InnerClassSingleton.getInstance();
//        InnerClassSingleton instance1 = InnerClassSingleton.getInstance();
//        System.out.println(instance==instance1); //true

//        new Thread(() -> {
//            InnerClassSingleton instance = InnerClassSingleton.getInstance();
//            System.out.println(instance);
//        }).start();
//
//        new Thread(() -> {
//            InnerClassSingleton instance1 = InnerClassSingleton.getInstance();
//            System.out.println(instance1);
//        }).start();

//        Constructor<InnerClassSingleton> declaredConstructor = InnerClassSingleton.class.getDeclaredConstructor();
//        declaredConstructor.setAccessible(true);
//        InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance();
//        InnerClassSingleton instance = InnerClassSingleton.getInstance();
//        System.out.println(innerClassSingleton==instance); //false

        InnerClassSingleton instance = InnerClassSingleton.getInstance();
//        ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("testSerializable"));
//        oss.writeObject(instance);
//        oss.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testSerializable"));
        InnerClassSingleton object = ((InnerClassSingleton) ois.readObject());
        System.out.println(instance==object);

    }
}
class InnerClassSingleton implements Serializable {

    static final long serialVersionUID =42L;

    private static class InnerClassHolder{
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){
        if (InnerClassHolder.instance!=null){
            throw new RuntimeException("单例不允许多个实例");
        }
    }
    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }

    Object readResolve() throws ObjectStreamException{
        return InnerClassHolder.instance;
    }
}

部分源码中的应用定位

感兴趣的可以去官网下载源码深入研究

Spring & JDK

java 复制代码
java.lang.Runtime
java 复制代码
org.springframework.aop.framework.ProxyFactoryBean
java 复制代码
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
java 复制代码
org.springframework.core.ReactiveAdapterRegistry

Tomcat

java 复制代码
org.apache.catalina.webresources.TomcatURLStreamHandlerFactory

反序列化指定数据源

java 复制代码
java.util.Currency

创作不易,点个关注吧!!!
创作不易,点个关注吧!!!
创作不易,点个关注吧!!!

相关推荐
nakyoooooo16 分钟前
【设计模式】工厂模式、单例模式、观察者模式、发布订阅模式
观察者模式·单例模式·设计模式
严文文-Chris2 小时前
【设计模式-享元】
android·java·设计模式
丶白泽3 小时前
重修设计模式-设计原则
设计模式·接口隔离原则·依赖倒置原则·开闭原则
【D'accumulation】3 小时前
典型的MVC设计模式:使用JSP和JavaBean相结合的方式来动态生成网页内容典型的MVC设计模式
java·设计模式·mvc
仙魁XAN4 小时前
Unity 设计模式 之 创造型模式-【工厂方法模式】【抽象工厂模式】
unity·设计模式·工厂方法模式·抽象工厂模式
龙哥·三年风水14 小时前
活动系统开发之采用设计模式与非设计模式的区别-后台功能总结
设计模式·php·tinkphp6
一头老羊15 小时前
前端常用的设计模式
设计模式
严文文-Chris16 小时前
【设计模式-组合】
设计模式
Magnetic_h16 小时前
【iOS】单例模式
笔记·学习·ui·ios·单例模式·objective-c
kimloner17 小时前
工厂模式(二):工厂方法模式
java·设计模式·工厂方法模式