面试必问设计模式之单例模式(超详细)

设计模式系列文章目录

如果本文对你们的开发之路有所帮助,请帮忙点个赞,您的支持是我坚持写博客的动力

【设计模式相关书籍】wx关注【Java从零学架构】,后 台回复【设计模式】自取

前言

上一篇文章带着大家输入学习了设计模式的工厂模式,这篇文章带着大家深入单例模式

项目代码见 https://gitee.com/janyxe/design_patterns

什么是单例模式

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

  • 当前JVM中只会有一个实例对象

能画出单例模式类图吗?并简单说明下

单例模式保证一个类只有一个实例

说说单例模式的应用场景

  • Servlet对象 默认为单例
  • 多线程的线程池的设计采用单例模式
  • Spring中Bean对象默认为单例模式
  • 定义枚举常量信息
  • 项目配置文件为单例模式

单例模式有哪些创建方式?

恶汉式懒汉式饿汉式(线程安全)双重检验锁枚举内部类静态类

懒汉模式

概念

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

懒汉模式实现
线程不安全版本

代码见 cn.jany.singleton.slacker.SlackerSingleton

/**
 * 懒汉模式线程不安全实现
 */
public class SlackerSingleton {

    private static SlackerSingleton slackerSingleton = null;

    private SlackerSingleton(){

    }

    /**
     * 获取实例方法
     * @return
     */
    public static SlackerSingleton getInstance(){
        if (slackerSingleton == null){
            // 当获取实例为null时创建实例
            slackerSingleton = new SlackerSingleton();
        }
        return slackerSingleton;
    }

    public static void main(String[] args) throws InterruptedException {
        SlackerSingleton slackerSingleton = SlackerSingleton.getInstance();
        SlackerSingleton slackerSingleton2 = SlackerSingleton.getInstance();
        // 控制台输出true,表示获取的实例为同一个
        System.out.println(slackerSingleton == slackerSingleton2);
    }
}

控制台输出true,代表两个对象一致

在多线程环境中存在多个对象 ,线程不安全

代码见 cn.jany.singleton.slacker.SlackerSingleton1

public class SlackerSingleton1 {


    public static void main(String[] args){

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                SlackerSingleton slackerSingleton = SlackerSingleton.getInstance();
                System.out.println(slackerSingleton);
            }
        });


        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                SlackerSingleton slackerSingleton = SlackerSingleton.getInstance();
                System.out.println(slackerSingleton);
            }
        });

        // 创建2个线程
        thread.start();
        thread2.start();
    }
}

输出对象不同

cn.jany.singleton.slacker.SlackerSingleton@4909e27e
cn.jany.singleton.slacker.SlackerSingleton@593fdda8
线程安全版本

通过synchronized关键字可以保证线程安全,不过效率相对较低

代码见 cn.jany.singleton.slacker.SlackerSingleton2

public class SlackerSingleton2 {

    private static SlackerSingleton2 slackerSingleton2 = null;

    public static synchronized  SlackerSingleton2 getInstance(){
        if (slackerSingleton2 == null){
            slackerSingleton2 = new SlackerSingleton2();
        }
        return slackerSingleton2;
    }

    public static void main(String[] args) {
        SlackerSingleton2 instance = SlackerSingleton2.getInstance();
        SlackerSingleton2 instance1 = SlackerSingleton2.getInstance();
        System.out.println(instance == instance1);

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(SlackerSingleton2.getInstance());
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(SlackerSingleton2.getInstance());
            }
        }).start();
    }

}
懒汉式双重检验锁
  • 通过双重检验锁加锁优化

  • 编译器(JIT),CPU 有可能对指令进行重排序
    类加载过程:分配空间、初始化、引用赋值
    重排序之后可能的结果:分配空间、引用赋值(多线程其他线程获取实例可能为未初始化的实例)、初始化
    通过volatile关键字进行修饰可以防止重排序

代码见 cn.jany.singleton.slacker.SlackerSingleton3

public class SlackerSingleton3 {

    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance == instance1);
    }

}
class Singleton{
    
    /**
     * volatile 防止重排序
     */
    private volatile static Singleton singleton = null;

    /**
     * 构造函数
     */
    private Singleton(){

    }

    /**
     * 获取实例
     * @return
     */
    public static Singleton getInstance(){
       // 第一层校验
       if (singleton == null){
           synchronized (Singleton.class){
               // 第二层校验
               if (singleton == null){
                   singleton = new Singleton();
               }
           }
       }
       return singleton;
    }


}

恶汉模式

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

  • 通过jvm加载机制保证实例唯一性(初始化只会执行一次)和线程安全(JVM 以同步方式完成类加载过程)

类加载过程会经过:

加载验证准备解析初始化使用卸载

类加载过程各阶段解析:

  • 加载:在硬盘查找并通过IO读取字节码文件,在加载节点生成这个类的java.class.Class对象
  • 验证:校验字节码文件的准确性
  • 解析:讲符号引用替换为直接引用
  • 初始化:对类的静态变量初始化为指定的值,执行静态代码块

只有在真正使用对应的类时,才会触发初始化

恶汉模式实现
恶汉模式实现方式一

代码见 cn.jany.singleton.villain.VillainSingleton

/**
 * 恶汉模式
 */
public class VillainSingleton {

    public static final  VillainSingleton villainSingleton = new VillainSingleton();

    /**
     * 构造方法
     */
    private VillainSingleton(){

    }

    /**
     * 获取实例
     * @return
     */
    private static VillainSingleton getInstance(){
        return villainSingleton;
    }

    public static void main(String[] args) {
        VillainSingleton instance = VillainSingleton.getInstance();
        VillainSingleton instance2 = VillainSingleton.getInstance();
        System.out.println(instance == instance2);
    }
}

控制台输出

true
恶汉模式实现方式二

代码见 cn.jany.singleton.villain.VillainSingleton2

/**
 * 恶汉模式
 */
public class VillainSingleton2 {

    public static final VillainSingleton2 singleton = new VillainSingleton2();

    /**
     * 构造函数
     */
    private VillainSingleton2(){

    }

    public static void main(String[] args) {
        VillainSingleton2 singleton = VillainSingleton2.singleton;
        VillainSingleton2 singleton1 = VillainSingleton2.singleton;
        System.out.println(singleton == singleton1);
    }
}

控制台输出

true

静态内部类

概念
  • 利用类的加载机制保证线程安全
  • 只有在实际使用的时候,才会触发类的初始化
静态内部类实现

代码见 cn.jany.singleton.inner.InnerClassSingletonTest

public class InnerClassSingletonTest {

    public static void main(String[] args) {
        InnerClassSingleton instance = InnerClassSingleton.getInstance();
        InnerClassSingleton instance2 = InnerClassSingleton.getInstance();
        System.out.println(instance == instance2);
    }

}

class InnerClassSingleton{

    static {
        System.out.println("InnerClassSingleton static ");
    }

    private InnerClassSingleton(){
    }

    public static InnerClassSingleton getInstance() {
        return SingletonHolder.instance;
    }

    private static class SingletonHolder {
        private static InnerClassSingleton instance= new InnerClassSingleton();

        static {
            System.out.println( "SingletonHolder static" );
        }
    }
}

控制台输出

InnerClassSingleton static 
SingletonHolder static
true

静态代码块

静态代码块实现

代码见 cn.jany.singleton.inner.StaticSingleton

/**
 * 静态代码块
 */
public class StaticSingleton {

    private static StaticSingleton staticSingleton;

    static {
        staticSingleton = new StaticSingleton();
    }

    public static StaticSingleton getInstance(){
        return staticSingleton;
    }

    public static void main(String[] args) {
        StaticSingleton instance = StaticSingleton.getInstance();
        StaticSingleton instance1 = StaticSingleton.getInstance();
        System.out.println(instance == instance1);
    }
}

控制台输出

true

枚举实现单例

代码见 cn.jany.singleton.enums.EnumSingletonTest

枚举实现单例实现
/**
 * 枚举实现单例模式
 */
public enum  EnumSingleton {

    INSTANCE;

}
public class EnumSingletonTest {

    public static void main(String[] args) {
        EnumSingleton instance = EnumSingleton.INSTANCE;
        EnumSingleton instance1 = EnumSingleton.INSTANCE;
        System.out.println(instance == instance1);
    }
}

控制台输出

true
  • 46
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 75
    评论
评论 75
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

janyxe

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值