单例模式的多种写法

目录

正文:

1.概念

2.饿汉式

3.懒汉式

3.1线程不安全的懒汉式

3.2线程安全的懒汉式

4.双重检查式

总结:


正文:

1.概念

单例模式(Singleton Pattern)是软件设计中常见的设计模式之一。它确保一个类只有一个实例,并提供一个全局访问点。这种模式在全局状态下的数据管理和控制、资源优化等方面非常有用。在Java中实现单例模式有多种方式,每种方式都有其特点和适用场景。

2.饿汉式

其特点是在类加载时就立即初始化实例,并且只创建一个实例。实例在类加载时就已经创建,所以在任何线程访问getInstance()方法之前,实例已经存在,因此不存在线程同步问题。

java 复制代码
public class EagerSingleton {
    // 私有静态实例,类加载时就初始化
    private static final EagerSingleton instance = new EagerSingleton();

    // 私有构造函数,防止外部通过new创建实例
    private EagerSingleton() {}

    // 公有的静态方法,提供全局访问点
    public static EagerSingleton getInstance() {
        return instance;
    }
}

优点:

  1. 实现简单:代码简洁,易于理解和维护。
  2. 无需同步:由于实例在类加载时就已经创建,不需要考虑线程同步问题。

缺点:

  1. 资源占用:不管是否需要,实例都会被创建,可能导致不必要的资源占用。
  2. 不易扩展:如果需要改变单例的创建逻辑,或者需要按需加载,饿汉式可能不是最佳选择。

适用场景:

  • 单例对象的创建过程不需要消耗大量资源。
  • 单例对象需要频繁访问,且访问速度很重要。
  • 确定单例对象的生命周期与应用程序的生命周期相同。

3.懒汉式

其核心特点是类实例在第一次使用时才创建,这种方式被称为"懒"初始化。懒汉式单例模式可以进一步细分为线程不安全的懒汉式和线程安全的懒汉式。

3.1线程不安全的懒汉式

这种实现方式的特点是在实例化对象时不进行同步处理,因此可能存在线程安全问题。

java 复制代码
public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton() {}

    //没有加锁
    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

如果在单线程环境下运行,这种方式是简单且高效的。但在多线程环境下,如果两个线程如果同时检查instance是否为null,并且都发现它是null,那么它们都可能创建一个新的实例,这将导致instance变量引用多个实例,违反了单例模式的原则。

3.2线程安全的懒汉式

为了解决线程安全问题,可以在创建实例时进行同步处理。可以通过使用synchronized关键字来实现。

java 复制代码
public class ThreadSafeLazySingleton {
    //防止内存可见性和重排序问题
    private static volatile ThreadSafeLazySingleton instance;

    private ThreadSafeLazySingleton() {}
     
    //通过加锁来保证线程安全
    public static synchronized ThreadSafeLazySingleton getInstance() {
        if (instance == null) {
            instance = new ThreadSafeLazySingleton();
        }
        return instance;
    }
}

在这个版本中,getInstance()方法被声明为synchronized,这意味着同一时间只有一个线程能够进入这个方法。volatile关键字确保了instance变量的读写操作对所有线程都是可见的,防止了内存中的变量值因线程内部缓存而出现不一致的情况。

优点:

  1. 延迟加载:实例在第一次使用时才会被创建,避免了资源浪费。
  2. 节省内存:只有在需要时才会创建实例,避免了一开始就创建对象的内存消耗。
  3. 适用于资源敏感型应用:适用于那些需要在应用启动时尽量减少资源占用的场景。

缺点:

  1. 线程不安全:在多线程环境下,可能会出现多个线程同时进入创建实例的逻辑,导致创建多个实例的问题。

  2. 需要额外考虑线程安全性:为了解决线程安全问题,需要在getInstance()方法上添加同步锁,会影响性能。

  3. 可能引起性能问题:由于需要在方法上添加同步锁,可能会导致性能下降,特别是在高并发环境下。

适用场景:

  • 资源敏感型应用:对资源占用有较高要求的应用,可以使用懒汉式单例来延迟实例化。

  • 单线程环境:在单线程环境下,懒汉式单例可以简单实现且没有线程安全问题。

  • 非高并发场景:在并发度不高的场景下,懒汉式单例可以满足需求。

4.双重检查式

双重检查锁定是一种优化的线程安全懒汉式实现,它通过两次检查实例是否已创建来减少同步的开销。

java 复制代码
public class Singleton {
    // 声明,并用volatile修饰,保证在多线程环境下的有序性
    private volatile static Singleton instance = null;
    // 私有构造方法
    private Singleton () {}
    // 对外提供一个获取实例的方法,
    public static Singleton getInstance() {
        // 使用双重if检查, 降低锁竞争的频率
        if (instance == null) {
            // instance没有被实例化才去加锁
            synchronized (Singleton.class) {
                // 获取到锁后再检查一遍,确认instance没有被实例化后去真正的实例化
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

在这个实现中,第一次检查instance是否为null是不加锁的,只有在instance确实为null时,才会进入同步块进行第二次检查和实例化。这种实现既保证了线程安全,又提高了性能,因为它避免了每次调用getInstance()时都进行同步。

优点:

  1. 性能优化:双重检查锁定的主要优点是它减少了每次获取单例实例时的同步开销。只有当实例尚未创建时,才会进行同步和创建实例。一旦实例被创建,后续的调用都不需要同步,从而提高了性能。

  2. 线程安全:通过在实例化过程中使用同步块,双重检查锁定确保了即使在多线程环境中,也只有一个实例被创建。

  3. 非强制性同步:与始终使用synchronized方法或块相比,双重检查锁定只在必要时进行同步,这避免了不必要的性能损失。

缺点:

  1. 复杂性:双重检查锁定的实现相对复杂,需要在代码中进行两次检查,容易引入错误和难以维护。

  2. 性能开销:虽然双重检查锁定可以减少加锁的频率,但仍需要进行两次检查,可能会引入性能开销。

使用场景:

双重检查锁定适用于以下场景:

  • 性能敏感的应用:当你需要频繁获取单例实例,并且实例化过程相对昂贵时,双重检查锁定可以提供更好的性能。

  • 多线程环境:在多线程环境中,你需要确保线程安全,同时希望减少同步的开销。

总结:

单例模式是一种常用的设计模式,通过保证一个类只有一个实例,提供全局访问点,方便对实例进行管理。在Java中,可以通过不同的实现方式来创建单例对象,每种实现方式都有其优缺点。在应用中,需要根据实际情况选择合适的单例实现方式,并注意线程安全和内存管理问题。

相关推荐
m0_571957581 小时前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解
魔道不误砍柴功3 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2343 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨3 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
测开小菜鸟5 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity6 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天6 小时前
java的threadlocal为何内存泄漏
java
caridle6 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^6 小时前
数据库连接池的创建
java·开发语言·数据库
苹果醋36 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx