单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。
通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。
单例模式是设计模式中最简单的形式之一。这一模式的目的是使得类的一个对象成为系统中的唯一实例。
要实现这一点,可以从客户端对其进行实例化开始。因此需要用一种只允许生成对象类的唯一实例的机制,"阻止"所有想要生成对象的访问。
单例模式的分类
单例模式可以根据实现方式的不同,分为以下几种分类:
- 饿汉式(Eager Initialization):在类加载的时候就创建并初始化单例对象。这种方式简单直接,线程安全,但可能会造成资源浪费,因为即使没有使用单例对象,它也会被提前创建。
- 懒汉式(Lazy Initialization):在第一次使用时创建单例对象。这种方式避免了不必要的资源浪费,但需要考虑线程安全性,确保在多线程环境下仍然能够正确地创建单例对象。
- 双重检验锁(Double-Checked Locking):结合了懒汉式和饿汉式的优点,在多线程环境下既能保证线程安全,又能延迟单例对象的创建。这种方式通过加锁来控制并发访问,并使用 volatile 关键字来保证可见性。
- 静态内部类(Static Inner Class):利用类加载机制来实现延迟加载和线程安全的单例对象。通过定义一个私有的静态内部类,在这个静态内部类中创建单例对象,从而保证只有在第一次使用时才会触发单例对象的初始化。
- 枚举(Enum):枚举类型本身就是单例的。在Java中,枚举类型能够保证在任何情况下都只有一个实例。因此,可以直接使用枚举来实现单例模式。
这些分类方式基本上涵盖了常见的单例模式实现方式。根据具体的需求和场景,选择适合的单例模式实现方式可以提高代码的可靠性和性能效率。
Singleton模式的使用场景
单例模式是一种常见的设计模式,在以下情况下可以考虑使用单例模式:
1.全局资源共享:当应用程序需要在多个部分共享同一个资源时,可以使用单例模式确保只有一个实例存在。例如,数据库连接池、日志记录器等全局资源可以使用单例模式来管理和访问。
2.对象缓存:当需要缓存对象以提高性能时,可以使用单例模式来管理缓存。通过保持单例实例,可以避免重复创建对象,并且在需要时可以快速获取缓存对象。
3.配置信息管理:当应用程序需要维护一些全局配置信息时,可以使用单例模式来管理这些配置。这样可以确保只有一个实例保存和管理配置信息,并且可以在程序的各个地方使用。
4.日志记录器:在应用程序中,通常需要一个日志记录器来记录系统的操作和异常信息。通过使用单例模式,可以方便地在代码的任何地方访问和使用统一的日志记录器。
5.系统计数器:某些场景需要记录系统某些操作的次数,如请求处理次数、任务处理次数等。使用单例模式可以方便地实现对统计数据的更新和访问。
需要注意的是,单例模式并不适用于所有的场景。在一些情况下,它可能会导致代码的复杂性增加,或者造成不必要的性能开销。
饿汉式
指全局的单例实例在类装载时构建。它是线程安全的,但是如果这个类我一直不使用,由于类初始化时,就已经实例它了,所以它会一直占着资源不释放。
java
/**
* 单例模式
*/
public class Singleton {
// 饿汉式
private static Singleton instance2 = new Singleton();
public static Singleton getInstance3(){
return instance2;
}
}
懒汉式--线程不安全
最基础的实现方式,线程上下文单例,不需要共享给所有线程,也不需要加synchronize之类的锁,以提高性能,有个致命缺点,就是在两个相同的线程中同时调用了getInstance1()
时,就会在这两个线程中产生不同的Singleton 对象。单例的作用就相当没有了。由于它的线程不安全,所以有了下面的方式。
java
/**
* 单例模式
*/
public class Singleton {
private static Singleton instance;
// 懒汉式---线程不安全
public static Singleton getInstance1(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
懒汉式--线程安全
加上synchronize之类保证线程安全的基础上的懒汉模式,相对性能很低,大部分时间并不需要同步。
java
/**
* 单例模式
*/
public class Singleton {
private static Singleton instance;
// 懒汉式---线程安全
public static synchronized Singleton getInstance2(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
它是线程安全了;但由于它是同步方法,在多线程调用它时,都会synchronized下,从而效率低下。在使用的过程中为了提高效率,所以我们有了如下方式
双重检验锁
在懒汉式基础上利用synchronize关键字和volatile关键字确保第一次创建时没有线程间竞争而产生多个实例,仅第一次创建时同步,性能相对较高
java
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
// 私有构造函数,防止外部创建实例
}
public static synchronized Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
在上述代码中,getInstance() 方法使用了双重检验锁,首先检查 instance 是否为 null,如果是,则再进入同步块进行二次检查,确保只有一个线程能够创建实例。同时,为了避免由于指令重排序而导致的问题,需要给 instance 声明为 volatile,保证可见性。
静态内部类(登记式)
创建类的全局属性存在,创建类被装载时创建。
java
public class Singleton {
private static Map<String, Singleton> registry = new HashMap<>();
private Singleton() {
// 私有构造函数,防止外部实例化
}
public static synchronized Singleton getInstance(String key) {
if (!registry.containsKey(key)) {
registry.put(key, new Singleton());
}
return registry.get(key);
}
}
第一次记载Singleton
时并不会初始化instance
,只有第一次调用getInstance4()
时才会实例化。它不仅保证线程安全、也能保证对象的唯一性,同时也延迟了单例的实例化。它也是最为推荐的一种单例模式