总结单例模式的写法

一、单例模式的概念

1.1 单例模式的概念

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。就是当前进程确保一个类全局只有一个实例。

1.2 单例模式的优点

  • 单例模式在内存中只有一个实例,减少了内存开支
  • 单例模式只生成一个实例,所以减少了系统的性能开销
  • 单例模式可以避免对资源的多重占用
  • 单例模式可以在系统设置全局的访问点

1.3 单例模式的缺点

  • 单例模式一般没有接口,扩展很困难
  • 单例模式不利于测试
  • 单例模式与单一职责原则有冲突

1.4使用场景

  • 要求生成唯一序列号的环境
  • 在整个项目中需要一个共享访问点或共享数据
  • 创建一个对象需要消耗的资源过多
  • 需要定义大量的静态常量和静态方法(如工具类)的环境

2.懒汉式

懒汉式,顾名思义就是实例在用到的时候才去创建,"比较懒",用的时候才去检查有没有实例,如果有则直接返回,没有则新建。

2.1 懒汉式(加锁)

java 复制代码
public class Singleton {
    private static Singleton instance;
    private Singleton() {
    }
    public synchronized static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

这种把锁直接方法上的办法,所有的访问都需要获取锁,导致了资源的浪费。

2.2 懒汉式(双重校验锁)

java 复制代码
public class Singleton{
    //volatile修饰,防止指令重排
    private static volatile Singleton instance;
    private Singleton() {
    }
    public static Singleton getInstance() {
        //第一重校验,检查实例是否存在
        if (instance == null) {
            //同步块
            synchronized (Singleton.class) {
                //第二重校验,检查实例是否存在,如果不存在才真正创建实例
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

}

我们把synchronized加在了方法的内部,一般的访问是不加锁的,只有在instance==null的时候才加锁。

(1)为什么要双重校验?

如果不双重校验。

如果两个线程一起调用getInstance方法,并且都通过了第一次的判断instance==null,那么第一个线程获取了锁,然后实例化了instance,然后释放了锁,然后第二个线程得到了线程,然后马上也实例化了instance。这就不符合我们的单例要求了。

(2)为什么要用volatile 修饰 instance?

防止指令重排。

那这个重排指的是哪?指的是instance = new Singleton(),我们感觉是一步操作的实例化对象,实际上对于JVM指令,是分为三步的:

  • 分配内存空间
  • 初始化对象
  • 将对象指向刚分配的内存空间

有些编译器为为了性能优化,可能会把第二步和第三步进行重排序,顺序就成了:

  • 分配内存空间
  • 将对象指向刚分配的内存空间
  • 初始化对象

3. 饿汉式

饿汉式,就像它的名字,饥不择食,定义的时候直接初始化。

java 复制代码
public class Singleton{
    private static Singleton instance=new Singleton();
    private Singleton() {
    }
    public static Singleton getInstance() {
        return instance;
    }
 
}

因为instance是个静态变量,所以它会在类加载的时候完成实例化,不存在线程安全的问题。

这种方式不是懒加载,不管我们的程序会不会用到,它都会在程序启动之初进行初始化。

4. 静态内部类

静态内部类是更进一步的写法,不仅能实现懒加载、线程安全,而且JVM还保持了指令优化的能力。

java 复制代码
public class Singleton{
    private Singleton() {
    }
    private static class InnerSingleton {
        private static final Singleton instance = new Singleton();
    }
    public static Singleton getInstance() {
        return InnerSingleton.instance;
    }
}

Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会加载静态内部类InnerSingleton类,从而完成Singleton的实例化。

类的静态属性只会在第一次加载类的时候初始化,同时类加载的过程又是线程互斥的,JVM帮助我们保证了线程安全。

5. 枚举

通过enum修饰Singleton单例类,仅需定义一个INSTANCE,然后在静态方法实例化方法getInstance中直接返回INSTANCE即可。

java 复制代码
public enum Singleton {
    INSTANCE;
    public static Singleton getInstance(){
        return INSTANCE;
    }
}

6 总结

饿汉模式:线程是安全的,只进行读操作

懒汉模式:不安全,有读操作也有写操作

单例模式的选择取决于具体需求,如是否需要懒加载、是否需要线程安全等。每种实现方式都有其适用场景和潜在问题,设计时应根据具体情况选择最合适的实现方法。

相关推荐
L_cl18 小时前
Python学习从0到1 day27 Python 高阶技巧 ③ 设计模式 — 单例模式
学习·单例模式·设计模式
ktkiko112 天前
Java中的设计模式——单例模式、代理模式、适配器模式
java·单例模式·设计模式
傻傻虎虎3 天前
【真题笔记】21年系统架构设计师案例理论点总结
单例模式·系统架构·uml·命令模式
Mr. zhihao3 天前
享元模式及其运用场景:结合工厂模式和单例模式优化内存使用
单例模式·享元模式
南城花随雪。3 天前
Spring框架之单例模式 (Singleton Pattern)
java·spring·单例模式
编程、小哥哥3 天前
设计模式之单列模式(7种单例模式案例,Effective Java 作者推荐枚举单例模式)
java·单例模式·设计模式
·Lntano·远方3 天前
线程安全的单例模式
java·开发语言·单例模式
xianwu5434 天前
【单例模式】饿汉式与懒汉式以及线程安全
linux·开发语言·网络·c++·git·安全·单例模式
工业甲酰苯胺4 天前
C# 单例模式的多种实现
javascript·单例模式·c#
WaaTong4 天前
《重学Java设计模式》之 单例模式
java·单例模式·设计模式