Java——》谈谈你对单例模式的理解

推荐链接:
    总结——》【Java】
    总结——》【Mysql】
    总结——》【Spring】
    总结——》【SpringBoot】

Java——》谈谈你对单例模式的理解

  • 一、单例模式的作用
  • 二、单例模式的实现
    • 1、饿汉式
    • 2、懒汉式(锁式)
    • 3、懒汉式(双重检测锁式)
    • 4、静态内部类式
    • 5、枚举单例
  • 三、单例模式的破解
    • 1、反射
      • 1)反射破坏单例
      • 2)禁止反射破坏单例
    • 2、序列化和反序列
      • 1)序列化和反序列破坏单例
      • 2)禁止序列化和反序列破坏单例
  • 四、哪些框架(场景)中用到了单例模式的设计

一、单例模式的作用

单例模式的核心是保证一个类只有一个实例,并且提供一个访问实例的全局访问点。

二、单例模式的实现

实现方式 优缺点
饿汉式 线程安全,调用效率高 ,但是不能延迟加载,可能造成空间资源浪费
懒汉式:锁式 线程安全,调用效率不高synchronized,能延迟加载
懒汉式:双重检测锁式 在懒汉式的基础上解决并发问题
静态内部类式 线程安全,资源利用率高,可以延时加载
枚举单例 线程安全,调用效率高,但是不能延迟加载

1、饿汉式


/**
 * 单例模式:饿汉式
 */
public class SingletonForHungry {

    // 缺点:如果对象没有被使用,造成空间资源浪费
    private byte[] b1 = new byte[1024 * 1024];
    private byte[] b2 = new byte[1024 * 1024];
    private byte[] b3 = new byte[1024 * 1024];

    // 声明此类型的变量,并实例化,当该类被加载的时候就完成了实例化并保存在了内存中
    private static final SingletonForHungry singletonInstance = new SingletonForHungry();

    // 私有化所有的构造方法,防止直接通过new关键字实例化
    private SingletonForHungry() {
    }

    // 对外提供一个获取实例的静态方法
    public static SingletonForHungry getSingletonInstance() {
        return singletonInstance;
    }

    public static void main(String[] args) {
        System.out.println(SingletonForHungry.singletonInstance);
        System.out.println(SingletonForHungry.singletonInstance);
    }
}

2、懒汉式(锁式)

/**
 * 单例模式:懒汉式(锁式)
 */
public class SingletonForLazySynchronized {
    // 声明此类型的变量,但没有实例化
    private static SingletonForLazySynchronized singletonInstance;

    // 私有化所有的构造方法,防止直接通过new关键字实例化
    private SingletonForLazySynchronized() {

    }

    // 对外提供一个获取实例的静态方法,为了数据安全添加synchronized关键字
    public static SingletonForLazySynchronized getSingletonInstance() {
        if (singletonInstance == null) {
            singletonInstance = new SingletonForLazySynchronized();
        }
        return singletonInstance;
    }

    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                System.out.println(SingletonForLazySynchronized.getSingletonInstance());
            }).start();
        }
    }
}

3、懒汉式(双重检测锁式)

/**
 * 单例模式:懒汉式(双重检测锁式)
 */
public class SingletonForLazyDoubleCheck {

    // 声明此类型的变量,但没有实例化
    // 必须加volatile,防止指令重排序
    private static volatile SingletonForLazyDoubleCheck singletonInstance;

    // 私有化所有的构造方法,防止直接通过new关键字实例化
    private SingletonForLazyDoubleCheck() {
    }

    // 对外提供一个获取实例的静态方法
    public static SingletonForLazyDoubleCheck getSingletonInstance() {
        if (singletonInstance == null) {
            synchronized (SingletonForLazyDoubleCheck.class) {
                // 双重检测
                if (singletonInstance == null) {
                    // 加volatile防止指令重排序
                    // 1.分配内存空间
                    // 2.创建实例对象
                    // 3.把这个内存地址赋值给变量的引用
                    singletonInstance = new SingletonForLazyDoubleCheck();
                }
            }
        }
        return singletonInstance;
    }

    public static void main(String[] args) throws Exception {
        System.out.println(DoubleCheckSingleton.getSingletonInstance());
        System.out.println(DoubleCheckSingleton.getSingletonInstance());
    }

}

4、静态内部类式

/**
 * 单例模式:静态内部类
 */
public class SingletonForStaticInner {

    // 静态内部类
    public static class SingletonClassInstance {
        // 声明外部类型的静态常量
        public static final SingletonForStaticInner singletonInstance = new SingletonForStaticInner();
    }

    // 私有化构造方法
    private SingletonForStaticInner() {

    }

    // 对外提供的唯一获取实例的方法
    public static SingletonForStaticInner getSinletonInstance() {
        return SingletonClassInstance.singletonInstance;
    }

    public static void main(String[] args) {
        System.out.println(SingletonForStaticInner.getSinletonInstance());
        System.out.println(SingletonForStaticInner.getSinletonInstance());
    }
}

5、枚举单例

/**
 * 单例模式:枚举
 */
public enum SingletonForEnum {
    INSTANCE;

    public static void main(String[] args) {
        System.out.println(SingletonForEnum.INSTANCE);
        System.out.println(SingletonForEnum.INSTANCE);
    }
}

三、单例模式的破解

1、反射

Q:为什么反射可以破坏单例模式?
A:因为反射可以操作私有的属性和方法,通过私有的构造器来创建实例。

Q:可以用反射创建枚举对象吗?
A:不可以,源码中有判断。

1)反射破坏单例

package com.example.test.singleton;

import java.lang.reflect.Constructor;

/**
 * 单例模式:懒汉式(双重检测锁式)
 */
public class SingletonForLazyDoubleCheck {

    // 声明此类型的变量,但没有实例化
    // 必须加volatile,防止指令重排序
    private static volatile SingletonForLazyDoubleCheck singletonInstance;


    // 私有化所有的构造方法,防止直接通过new关键字实例化
    private SingletonForLazyDoubleCheck() {
    }

    // 对外提供一个获取实例的静态方法
    public static SingletonForLazyDoubleCheck getSingletonInstance() {
        if (singletonInstance == null) {
            synchronized (SingletonForLazyDoubleCheck.class) {
                // 双重检测
                if (singletonInstance == null) {
                    // 加volatile防止指令重排序
                    // 1.分配内存空间
                    // 2.创建实例对象
                    // 3.把这个内存地址赋值给变量的引用
                    singletonInstance = new SingletonForLazyDoubleCheck();
                }
            }
        }
        return singletonInstance;
    }

    public static void destorySigleton() throws Exception {
        // 反射可以暴力破解
        Class<SingletonForLazyDoubleCheck> cls = SingletonForLazyDoubleCheck.class;
        SingletonForLazyDoubleCheck singleton = cls.newInstance();
        System.out.println("反射获取实例对象 = " + singleton);

        Constructor<?>[] declaredConstructors = cls.getDeclaredConstructors();
        declaredConstructors = cls.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            declaredConstructor.setAccessible(true);
            Object o = declaredConstructor.newInstance(null);
            System.out.println("反射获取实例对象 = " + o);
        }
    }

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

}


Java——》谈谈你对单例模式的理解_第1张图片

2)禁止反射破坏单例

解决的方式是在无参构造方法中手动抛出异常控制,或者声明一个全局变量来控制。

package com.example.test.singleton;

import java.lang.reflect.Constructor;

/**
 * 单例模式:懒汉式(双重检测锁式)
 */
public class SingletonForLazyDoubleCheck {

    // 声明此类型的变量,但没有实例化
    // 必须加volatile,防止指令重排序
    private static volatile SingletonForLazyDoubleCheck singletonInstance;

    private static boolean flag = false; // false 表示对象还没有被创建


    // 私有化所有的构造方法,防止直接通过new关键字实例化
    private SingletonForLazyDoubleCheck() {
        if(!flag){
            // 说明是第一次创建
            flag = true;
            singletonInstance = this;
        }else{
            throw  new RuntimeException("已经有了对象,就不要再创建了...");
        }
    }

    // 对外提供一个获取实例的静态方法
    public static SingletonForLazyDoubleCheck getSingletonInstance() {
        if (singletonInstance == null) {
            synchronized (SingletonForLazyDoubleCheck.class) {
                // 双重检测
                if (singletonInstance == null) {
                    // 加volatile防止指令重排序
                    // 1.分配内存空间
                    // 2.创建实例对象
                    // 3.把这个内存地址赋值给变量的引用
                    singletonInstance = new SingletonForLazyDoubleCheck();
                }
            }
        }
        return singletonInstance;
    }

    public static void destorySigleton() throws Exception {
        // 反射可以暴力破解
        Class<SingletonForLazyDoubleCheck> cls = SingletonForLazyDoubleCheck.class;
        SingletonForLazyDoubleCheck singleton = cls.newInstance();
        System.out.println("反射获取实例对象 = " + singleton);

        Constructor<?>[] declaredConstructors = cls.getDeclaredConstructors();
        declaredConstructors = cls.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            declaredConstructor.setAccessible(true);
            Object o = declaredConstructor.newInstance(null);
            System.out.println("反射获取实例对象 = " + o);
        }
    }

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

}

2、序列化和反序列

1)序列化和反序列破坏单例

2)禁止序列化和反序列破坏单例

    // 重写该方法,防止序列化和反序列化获取实例
    private Object readResolve() throws ObjectStreamException {
        return singletonInstance;
    }

四、哪些框架(场景)中用到了单例模式的设计

1、Spring中的Bean对象(默认是单例模式)
2、相关的工厂对象,比如:MyBatis中的SqlSessionFactory,Spring中的BeanFactory
3、相关的配置对象,比如:MyBatis中的Configuration对象,SpringBoot中的各个XXAutoConfiguration对象
4、应用程序的日志框架
5、数据库连接池

你可能感兴趣的:(Java,java,单例模式,饿汉式,懒汉式,双重检测)