设计模式-单例模式

一、什么是单例模式

单例模式是一种创建型设计模式,它限制了一个类只能实例化一个对象,并提供一个全局访问点以供其他对象使用

二、为什么要用单例模式

  1. 避免了资源浪费:保证了全局唯一实例,这样可以确保系统的行为一致性,避免了资源的重复创建与浪费。

  2. 访问简单:通过单例模式,其他对象可直接访问,不需要考虑复杂参数传递及上下文切换

  3. 避免了对同一资源的竞争冲突:对于需要共享资源的场景,单例模式可以避免对同一资源的竞争冲突。

  4. 实现了延迟加载:只有在需要使用时才创建实例对象,减少了系统的启动时间和资源占用。

  5. 可维护性强:只有一个实例对象,不需要考虑多个实例之间的一致性问题,只需要关注一个实例对象的状态和行为。

总的来说,单例模式可以提供全局唯一的实例对象,简化了代码的设计和实现,提高了系统的性能和可维护性。但需要注意的是,滥用单例模式可能导致代码的可测试性和可扩展性降低,因此在使用时需谨慎考虑。

三、单例模式的多种实现

1. 懒汉式:

  • 线程不安全:在第一次调用获取实例方法时才创建实例对象。
  • 线程安全:使用同步锁或双重检查锁定(Double-Checked Locking)机制来确保线程安全。

懒汉式实现示例如下:

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

    // 私有构造方法,防止外部直接实例化
    private LazySingleton() {}

    // 获取唯一实例的方法
    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

在懒汉式中,使用了一个静态变量 instance 来保存实例对象,初始时为 null。在 getInstance() 方法中,首先判断 instance 是否为 null,如果是,则创建一个新的实例对象,然后返回。如果 instance 已经存在,则直接返回现有的实例对象。这样可以实现延迟加载,只有在第一次调用 getInstance() 方法时才会创建实例。

需要注意的是,懒汉式的简单实现在多线程环境下是线程不安全的。如果多个线程同时调用 getInstance() 方法,有可能会创建多个实例对象。为了确保线程安全,可以在 getInstance() 方法上加上同步锁,或者使用双重检查锁定(Double-Checked Locking)机制。

2. 饿汉式:

  • 在类加载时就创建实例对象,因此线程安全。

枚举方式实现示例如下:

java 复制代码
public class Singleton {
    // 创建私有的静态实例对象
    private static Singleton instance = new Singleton();
    
    // 私有化构造函数,防止外部直接创建实例对象
    private Singleton() {
        // do something
    }
    
    // 提供公共的静态访问方法,返回实例对象
    public static Singleton getInstance() {
        return instance;
    }
}
 

3. 枚举方式:

  • 枚举类型本身就是单例模式,枚举类型的实例在类加载时就被实例化,因此线程安全。

枚举方式实现示例如下:

java 复制代码
public enum Singleton {
    INSTANCE;
    
    // 添加自己需要的属性和方法
    private int count;
    
    private Singleton() {
        // do something
    }
    
    public int getCount() {
        return count;
    }
    
    public void setCount(int count) {
        this.count = count;
    }
    
    // 添加自己需要的其他方法
    public void incrementCount() {
        count++;
    }
}

在上述代码中,Singleton枚举类型只有一个实例 INSTANCE。私有的构造函数保证了其他类不能直接实例化Singleton。可以在枚举类型中添加所需的属性和方法。

使用示例:

java 复制代码
public class Main {
    public static void main(String[] args) {
        Singleton instance = Singleton.INSTANCE;
        instance.setCount(10);
        System.out.println("Count: " + instance.getCount()); // Output: Count: 10

        Singleton anotherInstance = Singleton.INSTANCE;
        anotherInstance.incrementCount();
        System.out.println("Count: " + instance.getCount()); // Output: Count: 11
    }
}

在上述示例中,我们可以通过Singleton.INSTANCE来访问单例实例。两次获取到的实例对象是相同的,因此对实例的操作是共享的。

枚举方式实现的单例模式具有线程安全性,同时也可以防止反射和反序列化攻击。因此,枚举方式是实现单例模式的推荐方式之一。

4. 双重校验锁(Double-Checked Locking):

  • 使用同步锁和双重检查来确保只有一个实例对象被创建。
  • 单例模式的双重检查锁(Double-Checked Locking)是一种延迟初始化的实现方式,既能保证线程安全性,又能提高性能。

双重检查锁实现示例如下:

java 复制代码
public class Singleton {
    private volatile static Singleton instance;
    
    // 添加自己需要的属性和方法
    private int count;
    
    private Singleton() {
        // do something
    }
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
    
    public int getCount() {
        return count;
    }
    
    public void setCount(int count) {
        this.count = count;
    }
    
    // 添加自己需要的其他方法
    public void incrementCount() {
        count++;
    }
}

在上述代码中,使用了关键字 volatile 来保证 instance 变量的可见性。在 getInstance() 方法中,首先检查 instance 是否已经被实例化,如果没有,再通过 synchronized 关键字加锁,进行实例化。这样可以避免多个线程同时进入实例化代码块,从而保证线程安全性。使用双重检查锁方式,可以避免每次调用 getInstance() 方法时都进行同步,提高了性能。

使用示例:

java 复制代码
public class Main {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        instance.setCount(10);
        System.out.println("Count: " + instance.getCount()); // Output: Count: 10

        Singleton anotherInstance = Singleton.getInstance();
        anotherInstance.incrementCount();
        System.out.println("Count: " + instance.getCount()); // Output: Count: 11
    }
}

在上述示例中,通过 Singleton.getInstance() 来获取单例实例。两次获取到的实例对象是相同的,因此对实例的操作是共享的。

双重检查锁方式实现的单例模式能够保证线程安全性,并且在实例化时加锁,避免了不必要的同步开销。因此,双重检查锁方式是一种推荐的单例模式实现方式。

5. 静态内部类:

  • 利用类的静态内部类特性,外部类加载时不会加载内部类,只有在第一次使用内部类时才会创建实例,因此具有延迟加载效果。

静态内部类实现示例如下:

java 复制代码
public class Singleton {
    private Singleton() {
        // do something
    }
    
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    
    // 添加自己需要的属性和方法
    private int count;
    
    public int getCount() {
        return count;
    }
    
    public void setCount(int count) {
        this.count = count;
    }
    
    // 添加自己需要的其他方法
    public void incrementCount() {
        count++;
    }
}

在上述代码中,私有的静态内部类 SingletonHolder 内部维护了一个私有的静态 final 实例 INSTANCE。在静态内部类中创建单例对象,利用了类加载的特性,保证只有在需要时才会加载静态内部类,从而实现了延迟初始化。而且静态内部类的加载是线程安全的。通过 getInstance() 方法返回单例对象。

使用示例:

java 复制代码
public class Main {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        instance.setCount(10);
        System.out.println("Count: " + instance.getCount()); // Output: Count: 10

        Singleton anotherInstance = Singleton.getInstance();
        anotherInstance.incrementCount();
        System.out.println("Count: " + instance.getCount()); // Output: Count: 11
    }
}

在上述示例中,通过 Singleton.getInstance() 来获取单例实例。两次获取到的实例对象是相同的,因此对实例的操作是共享的。

静态内部类实现的单例模式能够保证线程安全性,并且在需要时才创建单例对象。而且相比双重检查锁方式,静态内部类的方式更加简单、直观。因此,静态内部类方式也是一种常用的单例模式实现方式。

6. 上面5种方式的使用抉择:

这些实现方式各有优缺点,选择合适的方式取决于具体的使用场景和需求。在并发环境下,需要考虑线程安全性。在某些情况下,需要延迟加载实例对象,避免资源浪费。

四、Spring Boot中单例模式的使用方式

1. 使用@Component注解

在需要使用单例对象的类上使用@Component注解来标识,Spring Boot会自动扫描并创建该对象的单一实例。

java 复制代码
@Component
public class SingletonService {
    // ...
}

在其他类中可以通过@Autowired注解将SingletonService注入进来,并且每次注入的都是同一个实例。

2. 使用@Scope注解

使用@Scope注解来指定Bean的作用域为单例模式。

java 复制代码
@Component
@Scope("singleton")
public class SingletonService {
    // ...
}

这样定义的SingletonService就会在整个应用程序中被共享,每次注入或获取该Bean时都会得到同一个实例。

3. 使用@Lazy注解

使用@Lazy注解来延迟加载单例Bean,只有在需要使用时才会创建。

java 复制代码
@Component
@Lazy
public class SingletonService {
    // ...
}

在使用过程中,如果没有主动使用该Bean,Spring Boot不会创建该实例。只有在需要使用时,才会创建并注入。

相关推荐
思忖小下10 小时前
梳理你的思路(从OOP到架构设计)_简介设计模式
设计模式·架构·eit
liyinuo201712 小时前
嵌入式(单片机方向)面试题总结
嵌入式硬件·设计模式·面试·设计规范
yaosheng_VALVE12 小时前
探究全金属硬密封蝶阀的奥秘-耀圣控制
运维·eclipse·自动化·pyqt·1024程序员节
dami_king12 小时前
SSH特性|组成|SSH是什么?
运维·ssh·1024程序员节
aaasssdddd9614 小时前
C++的封装(十四):《设计模式》这本书
数据结构·c++·设计模式
T1an-114 小时前
设计模式之【观察者模式】
观察者模式·设计模式
思忖小下16 小时前
梳理你的思路(从OOP到架构设计)_设计模式Factory Method模式
设计模式·工厂方法模式·eit
霁月风17 小时前
设计模式——工厂方法模式
c++·设计模式·工厂方法模式
发飙的蜗牛'19 小时前
23种设计模式
android·java·设计模式
重生之我在字节当程序员20 小时前
如何实现单例模式?
单例模式