首页 > 其他分享 >单例模式-饿汉、懒汉(线程安全、双重检查锁定)、静态内部类、枚举

单例模式-饿汉、懒汉(线程安全、双重检查锁定)、静态内部类、枚举

时间:2024-08-11 15:55:30浏览次数:12  
标签:Singleton 饿汉 private instance 线程 单例 static public

1 饿汉式

这种方式是最简单的单例模式实现,它在类加载的时候就创建了单例对象。
优点

  • 实现简单
  • 线程安全
    缺点
  • 无论是否使用,都会加载单例类并创建单例对象
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

2 懒汉式

2.1 懒汉式(线程不安全)

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

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

2.2 使用同步保证线程安全

优点

  • 线程安全
  • 延迟加载
    缺点
  • 效率较低,每次调用 getInstance() 都会同步阻塞
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

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

2.3 懒汉式(双重检查锁定)

这种方式使用双重检查锁定来减少同步开销。
优点

  • 线程安全
  • 延迟加载
  • 效率较高
public class Singleton {
    private volatile static Singleton instance;

    private Singleton() {}

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



解析
第一步:初次检查 instance 是否为 null
if (instance == null) {

  1. 初次检查:
  • 在进入 synchronized 块之前,先检查 instance 是否为 null。
  • 如果 instance 已经被初始化,那么就不需要再次初始化,可以直接返回已存在的实例。
  • 这个检查可以避免在每次调用 getInstance() 方法时都进行同步操作,提高了性能。

第二步:同步 Singleton.class 对象
synchronized (Singleton.class) {
2. 同步锁:

  • 使用 synchronized 关键字锁定 Singleton.class 对象。
  • 这里使用 Singleton.class 作为锁对象是因为它是静态的,并且在整个应用程序生命周期中是唯一的。
  • 当一个线程获取到这个锁时,其他试图获取相同锁的线程将被阻塞,直到锁被释放。
  • 这样可以确保在任何时候只有一个线程能够进入这个 synchronized 块。

第三步:二次检查 instance 是否为 null
if (instance == null) { instance = new Singleton(); } }
3. 二次检查:

  • 再次检查 instance 是否为 null。
  • 这一步是为了确保在其他线程已经初始化了 instance 的情况下,当前线程不会重复初始化。
  • 这个检查非常重要,因为如果没有这个检查,可能会发生以下问题:
    • 指令重排序:
      • JVM 或编译器可能会为了优化而重排指令顺序。例如,instance = new Singleton(); 可能会被重排为:
        a. 分配内存给 instance。
        b. 调用 Singleton 的构造函数初始化 instance。
        c. 将 instance 设置为指向新分配的内存地址。
      • 如果两个线程几乎同时进入 synchronized 块,其中一个线程完成了第 1 步和第 2 步,但还没有完成第 3 步,这时另一个线程进入 synchronized 块并执行了 new Singleton(),那么第二个线程可能会看到一个尚未完全构造好的 Singleton 实例。
    • 可见性:
      • 如果没有 synchronized 块,那么一个线程创建了 instance 并将其设置为非 null 后,其他线程可能看不到这个更新,因为 Java 内存模型允许线程缓存变量的值。
      • synchronized 块保证了 instance 的更新对所有线程都是可见的。

3 静态内部类

这种方式利用 Java 的类加载机制来保证初始化实例时只有一个线程。
优点

  • 线程安全
  • 延迟加载
  • 效率较高
public class Singleton {
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

4 枚举

利用枚举类型的特性来实现单例。
优点

  • 线程安全
  • 延迟加载
  • 防止反射攻击
  • 防止序列化攻击
public enum Singleton {
    INSTANCE;

    public void someMethod() {
        // ...
    }
}

标签:Singleton,饿汉,private,instance,线程,单例,static,public
From: https://www.cnblogs.com/sherioc/p/18353485

相关文章

  • 【Redis进阶】Redis单线程模型和多线程模型
    目录单线程为什么Redis是单线程处文件事件理器的结构文件处理器的工作流程总结文件事件处理器连接应答处理器命令请求处理器命令回复处理器多线程为什么引入多线程多线程架构多线程执行流程关于Redis的问题Redis为什么采用单线程模型Redis为什么要引入多线程呢......
  • Linux C++ 多线程编程
    LinuxC++多线程编程参考教程:c++:互斥锁/多线程的创建和unique_lock<mutex>的使用_mutex头文件vc++-CSDN博客1.编写unique_mutex1.1创建文件夹通过终端创建一个名为unique_mutex的文件夹以保存我们的VSCode项目,在/unique_mutex目录下打开vscode。rosnoetic@rosnoetic-Virt......
  • Python和多线程(multi-threading)
    在Python中,实现并行处理的方法有几种,但由于Python的全局解释器锁(GIL,GlobalInterpreterLock)的存在,传统意义上的多线程(使用threading模块)并不总能有效利用多核CPU来实现真正的并行计算。GIL确保任何时候只有一个线程在执行Python字节码。不过,仍然有几种方法可以绕过这个限制,......
  • Java - 多线程
    三种实现方式常用成员方法1.线程name默认“Thread-”+"序号"2.可以通过重写构造方法在创建时给线程命名线程的生命周期与状态同步代码块格式synchronized(锁对象){操作共享数据的代码}1.锁对象随机,但只要是有static修饰的唯一对象,一般写本类class文件,如MyTh......
  • 设计模式——单例设计模式
    单例设计模式如果你想创建一个类而且它在任何时刻只会有一个对象,那么你就应该使用单例类模式。此模式保证某个类在运行期间,只有一个实例对外提供服务,而这个类被称为单例类。懒汉式:只有调用getInstance方法时才创建对象.使用synchronized同步锁来保证防止多个线程同时调......
  • C#多线程并发编程深度探索:解锁async、await、Task与lock等关键字的奥秘
    一、多线程介绍1.什么是多线程多线程是指在一个应用程序中同时执行多个线程的能力。每个线程都是独立运行的,拥有自己的执行路径和资源。多线程编程能够充分利用多核处理器的计算能力,提高应用程序的性能和响应性,特别是在处理耗时任务和并行计算时效果显著。在C#中,线程是程序......
  • 线程间的礼貌竞争:避免活锁现象
    线程间的礼貌竞争:避免活锁现象在多线程编程中,我们经常会遇到各种同步问题,其中一种比较特殊的情况被称为“活锁”(livelock)。活锁是一种递归情况,多个线程在执行过程中不断重复某段代码逻辑,这些线程通常是为了给其他线程让路而主动放弃执行机会。这就像两个在狭窄走廊上相遇......
  • 【JavaEE初阶】线程安全的集合类
    ......
  • [设计模式]单例模式
    单例模式懒汉式,线程不安全publicclassSingleton{privatestaticSingletoninstance;privateSingleton(){}publicstaticSingletongetInstance(){if(instance==null){instance=newSingleton();}......
  • java创建线程的几种方式,以及它们之间的区别(面试常用)
    线程创建的几种方法继承Thread类。如果继承了Thread类,直接new一个对象就可以创建一个线程实现Runnable接口。如果实现了Runnable接口,则还需要用Thread的构造方法,才能创建一个线程//使用Thread类创建线程并启动线程publicclassThreadTestextendsThread{@Overrid......