首页 > 其他分享 >设计模式 - Singleton pattern 单例模式

设计模式 - Singleton pattern 单例模式

时间:2024-07-14 21:56:02浏览次数:11  
标签:Singleton getInstance pattern instance 实例 线程 单例 设计模式 public

文章目录

定义

在这里插入图片描述

单例模式是一种创建型设计模式,它用来保证一个类只有一个实例, 并且提供一个访问该实例的全局节点。其在很多场景中都有应用,比如数据库连接池、日志记录器、Spring中对象的创建等。

总的来说,单例模式在需要控制实例数量、确保全局唯一性的场景中被广泛应用。单例模式通过限制类的实例化对象为一个,可以确保全局唯一性的场景中被广泛应用,从而有助于控制资源访问、简化全局访问点、减少内存占用等,在很多情况下都可以提升程序的运行效率。

单例模式的实现构成

构成

一个私有的构造函数、一个私有的静态变量以及一个共有的静态函数。

其中,私有构造函数保证了其他线程不能通过new来创建对象实例,而共有的静态函数则是用来后续所有对此函数的调用都返回唯一的私有静态变量。

UML图

在这里插入图片描述

单例模式的六种实现

懒汉式-线程不安全

下面实现中,instance 被延迟实例化,这样的话,当没有使用到这个类的话,就会节约资源,不会实例化 LazySingletonsAreNotSafe

但是该实现是线程不安全的,因为在多线程环境下,可以有多个线程同时进入 getInstance 方法,并且这个时候 instance 还未实例化,那么它们就都可以进入到 if 逻辑中,执行实例化操作,从而导致线程不安全问题。

public class LazySingletonsAreNotSafe {
    private static LazySingletonsAreNotSafe instance;

    private LazySingletonsAreNotSafe() {}

    public static LazySingletonsAreNotSafe getInstance() {
        if (instance == null) {
            instance = new LazySingletonsAreNotSafe();
        }
        return instance;
    }
}

懒汉式-线程安全

那么,如何可以保证线程安全呢?

其实,上一个实现方式中,线程不安全就是因为 instance 的实例化被执行了很多次,所以我们只要对 getInstance 方法进行加锁,保证同一个时间点只有一个线程可以进入该方法进行实例化操作,那么就保证了线程安全问题。
实现代码如下:

public class LazySingletonsAreSafe {
    private static LazySingletonsAreSafe instance;

    private LazySingletonsAreSafe() {}

	// 关键点:synchronized进行了加锁操作,从而保证线程安全。
    public static synchronized LazySingletonsAreSafe getInstance() {
        if (instance == null) {
            instance = new LazySingletonsAreSafe();
        }
        return instance;
    }
}

饿汉式-线程安全

对于懒汉式方法,如果不加锁会导致线程安全问题,而加锁虽然会保证线程安全,但是也带来了一定程度上的性能损耗,因此可以采用饿汉式。
懒汉式线程安全问题的原因是 getInstance 方法可能被执行多次,从而导致被实例化多次。所以我们采用在类加载的时候,直接实例化 instance ,这样就会避免实例化多次的问题。

当然,因为我们一开始在类加载的时候对象就被实例化了,所以也不会有延迟实例化种可以节约资源的优点。

public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();

    private EagerSingleton() {}

    public static EagerSingleton getInstance() {
        return instance;
    }
}

双重校验锁-线程安全

双重校验锁先判断 uniqueInstance 是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁。

public class DoubleCheckedLockingSingleton {
	// 注意:volatile 修饰
    private static volatile DoubleCheckedLockingSingleton instance;

    private DoubleCheckedLockingSingleton() {}

    public static DoubleCheckedLockingSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckedLockingSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckedLockingSingleton();
                }
            }
        }
        return instance;
    }
}

问题1: 为什么两个if?

if (instance == null) {
     synchronized (DoubleCheckedLockingSingleton.class) {
         if (instance == null) {
             instance = new DoubleCheckedLockingSingleton();
         }
     }
 }

第一个if是因为:高并发场景下,还是可能有不止一个线程成功的在 instance 还未初始化的时候就进入这里了,所以他们都会走下面的逻辑,所以加了一把锁,用来保证线程安全问题。
而第二个if则是因为:等到第一个线程执行完实例化之后,它会释放锁,这样的话下一个线程就会来拿这把锁,然后进行新一轮的实例化。所以,在锁里添加了第二个if用来进行判断,避免实例化多次。

问题2: 为什么 instancevolatile 进行修饰?

private static volatile DoubleCheckedLockingSingleton instance;

这个是因为 volatile 有禁止指令重排的功能。上述代码中单例对象有的时候可能会发生空指针异常的问题。

对于instance = new DoubleCheckedLockingSingleton(); 它其实是分为三个步骤来执行的:

  1. JVM为对象分配内存
  2. 在内存中进行对象的初始化
  3. 将内存对应的地址复制给instance

假设,现在有两个线程进入到了getInstance方法,当T1线程执行实例化操作时,T2线程在进行判断。

因为instance = new DoubleCheckedLockingSingleton();操作不是原子的,所以编译器可能会进行指令的重排序,即:

  1. JVM为对象分配内存
  2. 将内存对应的地址复制给instance
  3. 在内存中进行对象的初始化

这样的话,当T1线程执行完第二步地址复制给instance的时候,T2线程去进行判断,那么instance == null则是为true,所以会直接跳到最下面 return instance。从而导致空指针问题。

volatile可以避免指令重排,所以只要用volatile修饰instance就可以避免这个问题了。
在这里插入图片描述

静态内部类实现

BillPughSingleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 getUniqueInstance 方法从而触发 SingletonHolder.INSTANCESingletonHolder才会被加载,进行初始化。

public class BillPughSingleton {
    private BillPughSingleton() {}

    private static class SingletonHelper {
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }

    public static BillPughSingleton getInstance() {
        return SingletonHelper.INSTANCE;
    }
}

枚举实现

枚举实例的创建是线程安全的,而且在任何情况下都是它一个单例。在别的几种单例中,反序列化时会重新创建对象,而枚举单例则不存在这种情况。

public enum EnumSingleton {
    INSTANCE;

    public void someMethod() {
    }
}

总结

      
1. 饿汉式

    实现:在类加载时就完成了实例化。
    特点:线程安全,实现简单;但可能会造成资源浪费,因为即使不需要使用实例,也会在类加载时创建。
      
2. 懒汉式

    实现:在第一次调用 getInstance() 方法时进行实例化。
    特点:延迟加载,节省资源;但需要在 getInstance() 方法上加锁才可以保证线程安全,会影响性能。
      
3. 双重校验锁

    实现:在 getInstance() 方法中加入两次实例检查,第二次检查前加上锁,既保证了线程安全又提高了效率。
    特点:结合了懒汉式和饿汉式的优点,既实现了延迟加载,又优化了并发性能。
      
4. 静态内部类

    实现:将单例实例放在静态内部类中,当外部类被加载时静态内部类并不会被加载,只有在首次调用 getInstance() 方法时才会加载。
    特点:既实现了延迟加载,又保证了线程安全,且不需显式同步。
      
5. 枚举

    实现:利用枚举类型的特性来保证实例的唯一性。
    特点:线程安全,简洁易读,还能防止反序列化攻击。

其他设计模式文章:

标签:Singleton,getInstance,pattern,instance,实例,线程,单例,设计模式,public
From: https://blog.csdn.net/WLKQNYJY_SHT/article/details/140423999

相关文章

  • Java中的设计模式详解
    Java中的设计模式详解大家好,我是微赚淘客系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!单例模式在实际开发中,经常会遇到需要保证一个类只有一个实例的情况。单例模式通过私有化构造方法和静态方法来确保只有一个实例被创建。以下是一个示例:packagecn.juwatech.designpa......
  • 2024-07-13:用go语言,给定一个从0开始的长度为n的整数数组nums和一个从0开始的长度为m的
    2024-07-13:用go语言,给定一个从0开始的长度为n的整数数组nums和一个从0开始的长度为m的整数数组pattern,其中pattern数组仅包含整数-1、0和1。一个子数组nums[i..j]的大小为m+1,如果满足以下条件,则我们称该子数组与模式数组pattern匹配:1.若pattern[k]为1,则nums[i+k+1]>nums[i+k];......
  • 设计模式:从HttpServletRequestWrapper了解装饰者模式
    从一个参数处理的问题开始为了满足安全测试,需要给系统追加防XSS注入的功能,关于此类安全的问题,一般的解决方案就是在请求到达Controller之前,使用Web框架的Filter或者Spring本身的拦截器对HttpServletRequest对象的参数进行处理:@ComponentpublicclassMyInterceptorimpleme......
  • JAVA设计模式>>结构型>>适配器模式
    本文介绍23种设计模式中结构型模式的适配器模式目录1. 适配器模式1.1 基本介绍1.2 工作原理 1.3  适配器模式的注意事项和细节1.4  类适配器模式1.4.1 类适配器模式介绍1.4.2 应用实例 1.4.3注意事项和细节1.5 对象适配器模式1.5.1 基本介绍1......
  • 设计模式与分布式架构实战 总结
    在当今快速发展的软件工程领域,掌握设计模式和分布式架构对于构建高效、稳定、可扩展的系统至关重要。以下是对相关内容的进一步分析和梳理,供大家参考。架构设计的哲学:NP问题的现实映射什么是NP问题?NP问题是计算机科学中的一个重要概念,它代表了一类可以在多项式时间内验证......
  • java设计模式(十七)状态模式(State Pattern)
    1、模式介绍:状态模式(StatePattern)是一种行为型设计模式,用于实现对象状态的变化管理。它允许一个对象在其内部状态发生变化时改变其行为,使得对象看起来似乎修改了其类。2、应用场景:当一个对象的行为取决于其状态,并且需要在运行时根据状态改变其行为时。当状态转换过程中需......
  • java设计模式(十四)策略模式(Strategy Pattern)
    1、模式介绍:策略模式是一种行为设计模式,它定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。2、应用场景:当一个对象有多种行为,而需要动态选择一种行为时。不同的策略可以实现不同的行为,客户端根据需要在运行时选择合适的策略。当......
  • AC_patterns的生成过程
    at-speed技术用来测试电路正常工作的时序是否有问题,进而确定电路能否正常工作。这种技术测试的故障叫做transitionfault,同样这种技术也叫transitionfaulttest。想要实现这种技术,一对测试pattern(V1,V2),需要应用到测试电路当中(CircuitUnderTest,CUT)。V1用来初始化电......
  • c/c++设计模式---访问者模式
    访问者(Visitor)模式:访问器模式,行为型模式。  //(1)一个具体范例的逐渐演化  //阿司匹林肠溶片:改善血液流通,预防血栓形成,血栓形成就产生阻塞,人就会直接面临危险;  //氟伐他汀钠缓释片:降血脂。因为血脂高意味着血流慢,营养无法运输到身体各部位,还很可能引发心脑血管疾病;......
  • 设计模式之单例模式
    简介单例模式(SingletonPattern)是一种设计模式,其目的是确保一个类只有一个实例,并提供一个全局访问点来访问该实例。这在某些情况下非常有用,比如需要一个唯一的配置管理器、日志记录器、或资源管理器。单例模式的特点唯一实例:类内部维护一个唯一实例,确保类的实例只有一个......