【设计模式】(三)设计模式之单例模式

单例模式介绍

1. 描述

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,此模式保证某个类在运行期间,只有一个实例对外提供服务,而这个类被称为单例类。

单例模式也比较好理解,比如一个人一生当中只能有一个真实的身份证号,一个国家只有一个政府,类似的场景都是属于单例模式。

2. 单例模式要完成的两件事

  1. 保证一个类只有一个实例
  2. 为该实例提供一个全局访问节点

3. 单例模式结构

4. 单例模式实现的几种方式

4.1 饿汉式

在类加载期间初始化静态实例,保证 instance 实例的创建是线程安全的 ( 实例在类加载时实例化,有JVM保证线程安全).

​ 特点: 不支持延迟加载实例(懒加载) , 此中方式类加载比较慢,但是获取实例对象比较快

​ 问题: 该对象足够大的话,而一直没有使用就会造成内存的浪费。

java 复制代码
public class Singleton_01 {

    //1. 私有构造方法
    private Singleton_01(){

    }

    //2. 在本类中创建私有静态的全局对象
    private static Singleton_01 instance = new Singleton_01();


    //3. 提供一个全局访问点,供外部获取单例对象
    public static  Singleton_01 getInstance(){

        return instance;
    }

}

4.2 懒汉式(线程不安全)

这种方式的单例模式实现了懒加载,只有在调用getInstance()方法的时候才会去创建对象,但是如果是多线程情况,则会出现线程安全问题

java 复制代码
public class Singleton_02 {

    //1. 私有构造方法
    private Singleton_02(){

    }

    //2. 在本类中创建私有静态的全局对象
    private static Singleton_02 instance;


    //3. 通过判断对象是否被初始化,来选择是否创建对象
    public static  Singleton_02 getInstance(){

        if(instance == null){

            instance = new Singleton_02();
        }
        return instance;
    }

}

假设在单例类被实例化之前,有两个线程同时在获取单例对象,线程A在执行完if (instance == null) 后,线程调度机制将 CPU 资源分配给线程B,此时线程B在执行 if (instance == null)时也发现单例类还没有被实例化,这样就会导致单例类被实例化两次。为了防止这种情况发生,需要对 getInstance() 方法同步处理。改进后的懒汉模式.

4.3 懒汉式(线程安全)

使用同步锁 synchronized锁住 创建单例的方法 ,防止多个线程同时调用,从而避免造成单例被多次创建

  1. 即,getInstance()方法块只能运行在1个线程中

  2. 若该段代码已在1个线程中运行,另外1个线程试图运行该块代码,则 会被阻塞而一直等待

  3. 而在这个线程安全的方法里我们实现了单例的创建,保证了多线程模式下 单例对象的唯一性

java 复制代码
public class Singleton_03 {

    //1. 私有构造方法
    private Singleton_03(){

    }

    //2. 在本类中创建私有静态的全局对象
    private static Singleton_03 instance;


    //3. 通过添加synchronize,保证多线程模式下的单例对象的唯一性
    public static synchronized  Singleton_03 getInstance(){

        if(instance == null){
            instance = new Singleton_03();
        }
        return instance;
    }
}

懒汉式的缺点也很明显,我们给 getInstance() 这个方法加了一把大锁(synchronzed),导致这个函数的并发度很低。量化一下的话,并发度是 1,也就相当于串行操作了。而这个函数是在单例使用期间,一直会被调用。如果这个单例类偶尔会被用到,那这种实现方式还可以接受。但是,如果频繁地用到,那频繁加锁、释放锁及并发度低等问题,会导致性能瓶颈,这种实现方式就不可取了。

4.4 双重校验

饿汉式不支持延迟加载,懒汉式有性能问题,不支持高并发。那我们再来看一种既支持延迟加载、又支持高并发的单例实现方式,也就是双重检测实现方式。

实现步骤:

  1. 在声明变量时使用了 volatile 关键字,其作用有两个:

保证变量的可见性:当一个被volatile关键字修饰的变量被一个线程修改的时候,其他线程可以立刻得到修改之后的结果。

屏蔽指令重排序:指令重排序是编译器和处理器为了高效对程序进行优化的手段,它只能保证程序执行的结果时正确的,但 是无法保证程序的操作顺序与代码顺序一致。这在单线程中不会构成问题,但是在多线程中就会出现问题。

  1. 将同步方法改为同步代码块. 在同步代码块中使用二次检查,以保证其不被重复实例化 同时在调用getInstance()方法时不进行同步锁,效率高。
java 复制代码
public class Singleton_04 {

    //使用 volatile保证变量的可见性
    private volatile static Singleton_04 instance = null;

    private Singleton_04(){
    }

    //对外提供静态方法获取对象
    public static Singleton_04 getInstance(){
        //第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
        if(instance == null){
            synchronized (Singleton_04.class){
                //抢到锁之后再次进行判断是否为null
                if(instance == null){
                    instance = new Singleton_04();
                }
            }
        }
        return instance;
    }
}

4.5 静态内部类

  • 原理
    根据 静态内部类 的特性(外部类的加载不影响内部类),同时解决了按需加载、线程安全的问题,同时实现简洁
  1. 在静态内部类里创建单例,在装载该内部类时才会去创建单例
  2. 线程安全:类是由 JVM加载,而JVM只会加载1遍,保证只有1个单例
java 复制代码
public class Singleton_05 {

    private static class SingletonHandler{
        private static Singleton_05 instance = new Singleton_05();
    }

    private Singleton_05(){}

    public static Singleton_05 getInstance(){
        return SingletonHandler.instance;
    }
}

如果对你有帮助,可以关注博主(不定期更新各种技术文档) 给博主一个免费的点赞以示鼓励,谢谢 !

欢迎各位🔎点赞👍评论收藏⭐️

相关推荐
winks3几秒前
Spring Task的使用
java·后端·spring
秋意钟11 分钟前
Spring新版本
java·后端·spring
椰椰椰耶13 分钟前
【文档搜索引擎】缓冲区优化和索引模块小结
java·spring·搜索引擎
mubeibeinv14 分钟前
项目搭建+图片(添加+图片)
java·服务器·前端
青莳吖16 分钟前
Java通过Map实现与SQL中的group by相同的逻辑
java·开发语言·sql
Buleall23 分钟前
期末考学C
java·开发语言
重生之绝世牛码25 分钟前
Java设计模式 —— 【结构型模式】外观模式详解
java·大数据·开发语言·设计模式·设计原则·外观模式
小蜗牛慢慢爬行31 分钟前
有关异步场景的 10 大 Spring Boot 面试问题
java·开发语言·网络·spring boot·后端·spring·面试
shinelord明1 小时前
【再谈设计模式】享元模式~对象共享的优化妙手
开发语言·数据结构·算法·设计模式·软件工程
新手小袁_J1 小时前
JDK11下载安装和配置超详细过程
java·spring cloud·jdk·maven·mybatis·jdk11