• Java 中实现单例模式


    单例模式

    单例模式,就是一个类在任何情况下绝对只有一个实例,并且提供一个全局访问点来获取该实例。
    要实现单例,至少需要满足两个点:
    • 私有化构造方法,防止被外部实例化造成多实例问题
    • 提供一个静态方位作为全局访问点来获取唯一的实例对象
    在 Java 里面,至少有 6 种方法来实现单例。

    实现

    第一种

    第一种, 是最简单的实现,通过延迟加载的方式进行实例化,并且增加了同步 锁机制避免多线程环境下 的线程安全问题.
    1. public class Singleton {
    2. private static Singleton instance;
    3. private Singleton(){}
    4. public static synchronized Singleton getInstance(){
    5. if (instance == null){
    6. instance=new Singleton();
    7. }
    8. return instance;
    9. }
    10. }
    但是这种加锁会造成性能问题,而且同步锁只有在第一次实例化的时候才产生作用,后续不需要。

    第二种

    第二种,通过双重检查锁的方式,减少了锁的范围来提升性能 

    1. public class Singleton {
    2. private volatile static Singleton instance;
    3. private Singleton() {
    4. // 私有构造函数
    5. }
    6. public static Singleton getInstance() {
    7. if (instance == null) {
    8. synchronized (Singleton.class) {
    9. if (instance == null) {
    10. instance = new Singleton();
    11. }
    12. }
    13. }
    14. return instance;
    15. }
    16. }

    instance 使用 volatile 关键字修饰,以确保多线程环境下的可见性和有序性。

    使用volatile关键字修饰instance变量,主要是为了保证在多线程环境下获取单例实例的可见性和有序性。具体来说:

    1. 可见性:当一个线程第一次访问getInstance()方法时,如果instancenull,那么该线程将进入同步块并创建实例。这个写操作对于其他线程来说是可见的,即它们将立即看到instance的新值。这就避免了在一个线程创建实例后,其他线程仍然看到instancenull的情况。

    2. 有序性:在双重检查锁中,由于编译器和处理器的优化行为,可能会发生指令重排序。如果没有使用volatile关键字修饰instance,那么在某些情况下,其他线程可能会看到指令重排后的顺序,从而导致单例实例的未完全初始化。而使用volatile修饰instance后,禁止了这种指令重排序优化,保证了实例的完整性。

    第三种

    第三种,通过饿汉式实现单例。这种方式在类加载的时候就触发了实例化,从而避免了多线程同步问题。 
    1. public class Singleton {
    2. private static Singleton instance=new Singleton();
    3. private Singleton(){}
    4. public static Singleton getInstance(){
    5. return instance;
    6. }
    7. }

    第四种

    第四种,通过在静态块里面实例化,而静态块是在类加载的时候触发执行的,所以也只会执行一
    次。
    1. public class Singleton {
    2. private static Singleton instance = null;
    3. static {
    4. instance=new Singleton();
    5. }
    6. private Singleton(){}
    7. public Singleton getInstance(){
    8. return instance;
    9. }
    10. }
    上面两种方式,都是在类加载的时候初始化,没有达到延迟加载的效果,当然本身影响不大。

    第五种

    由于静态内部类只有调用静态内部类的方法,静态域,或者构造方法的时候才会加载静态内部类。
    1. public class Singleton {
    2. private static class SingletonHolder{
    3. private static final Singleton INSTANCE=new Singleton();
    4. }
    5. private Singleton(){}
    6. public static final Singleton getInstance(){
    7. return SingletonHolder.INSTANCE;
    8. }
    9. }
    所以当 Singleton 被加载的时候不会初始化 INSTANCE,从而实现了延迟加载。

    第六种

    我们还可以使用枚举类来实现。
    1. public enum Singleton {
    2. INSTANCE;
    3. // 添加其他成员变量和方法
    4. public void doSomething() {
    5. // 单例实例的操作
    6. }
    7. }
    这种写法既能避免多线程同步问题,又能防止反序列化重新创建新对象,也是一个比较好的方案。

    总结

    我认为大体分为 3 种方式来实现单例:
    • 第一种是通过双重检查锁的方式,它是一种线程安全并且是延迟实例化的方式,但是因为加锁,所以会有性能上的影响。
    • 第二种是通过静态内部类的方式实现,它也是一种延迟实例化,由于它是静态内部类,所以只会使用的时候加载一次,不存在线程安全问题。
    • 第三种是通过枚举类的方式实现,它既是线程安全的,又能防止反序列化导致破坏单例问题
    多线程、克隆、反序列化、反射,都有可能会造成单例的破坏。而我认为,通过枚举的方式实现单例,是能够解决所有可能被破坏的情况。
  • 相关阅读:
    【Docker学习】docker login/logout
    JavaScript浏览器(兼容、调试)
    SAP ABAP——数据类型(二)【TYPES自定义数据类型详解】
    关于良率:交期延误、报废补料、不做退款都是什么情况?
    R语言数据预处理:使用dplyr包进行数据预处理、使用mutate函数、factor函数把数值型变量转化为因子类型变量并使用labels函数指定因子标签
    微信小程序毕业设计题目计算机维修服务+后台管理系统|前后分离VUE.js
    如何更好的了解什么是集合
    前端研习录(09)——CSS浮动与清除浮动
    配置ftp及java链接上传文件到ftp
    《JavaEE》HTTPS
  • 原文地址:https://blog.csdn.net/qq_63431773/article/details/133824151