单例模式确保一个类只有一个实例,并提供一个全局访问点。以下是单例模式的几种常见实现方式:
一、饿汉式(线程安全)
public class Singleton {
// 1.静态 final 修饰,类加载时初始化
private static final Singleton instance = new Singleton();
// 2.私有构造方法,防止外部实例化
private Singleton() {}
// 3.提供全局访问点
public static Singleton getInstance() {
return instance;
}
}
// 在其他类中使用
public class OtherClass {
public void someMethod() {
// 获取单例实例
Singleton singleton = Singleton.getInstance();
// 调用单例实例的方法
singleton.doSomething();
}
}
为什么叫“饿汉式”?
“饿汉式”这个名字来源于它的行为特点。在饿汉式单例模式中,实例在类加载时就被创建,就好像一个饥饿的人(饿汉)迫不及待地吃掉食物一样。它在类加载时就“吃掉”了资源,确保了实例的唯一性和线程安全性。
为什么线程安全?
饿汉式单例模式的线程安全性主要依赖于Java的类加载机制。在Java中,类的静态变量在类加载时被初始化,并且类加载过程是由JVM保证线程安全的。因此,当多个线程同时调用getInstance()方法时,由于实例已经在类加载时被创建,所以不会出现多次创建实例的问题。
核心解析
static 和 final 的作用
static
static用于声明一个静态变量instance,它属于类本身而不是类的某个实例。静态变量在类加载时被初始化,并且在内存中只有一份副本,所有类的实例共享这个变量。
在单例模式中,static确保instance是类级别的变量,而不是实例级别的变量,这样可以保证只有一个实例存在。
final
final用于声明一个最终变量instance,它的值一旦被初始化后不能被修改。
在单例模式中,final确保instance只能被赋值一次,从而保证单例的唯一性。
结合使用的效果
确保只有一个 new Singleton()。通过 static 和 final 的结合,instance 在类加载时被初始化,并且只能被赋值一次。这样就确保了在整个应用程序中,只有一个 Singleton 实例被创建。
private Singleton() {} 的作用
将构造方法声明为私有,防止外部通过new Singleton()的方式创建类的实例。
在单例模式中,我们希望类的实例只能通过getInstance()方法获取,而不是通过直接调用构造方法创建。
如果没有私有构造方法,外部代码可以通过new Singleton()创建多个实例,破坏单例模式的唯一性。
public static Singleton getInstance() 的使用
提供一个公共的静态方法,用于获取单例实例。
通过这个方法,外部代码可以访问唯一的单例实例,而不需要知道实例的创建细节。
二、懒汉式(线程不安全)
为什么需要懒汉式单例模式?
1.资源优化
如果实例创建成本较高(如数据库连接池、大型对象等),而实例可能在整个应用程序运行过程中都不被使用,那么饿汉式会在类加载时就创建实例,造成资源浪费。懒汉式则在第一次使用时才创建实例,避免了这种浪费。
2.延迟初始化
在某些情况下,实例的初始化可能依赖于某些运行时条件或配置。懒汉式可以在满足这些条件时才初始化实例,从而提高系统的灵活性。
3.提高启动速度
如果实例的初始化过程较复杂且耗时,而应用程序在启动时并不立即需要该实例,那么懒汉式可以延迟初始化,从而提高应用程序的启动速度。
代码示例
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
为什么第一个方法是线程不安全的
第一个方法是线程不安全的,因为在多线程环境下,可能会有多个线程同时通过 if (instance == null) 检查,从而导致多次创建 Singleton 实例。
示例说明
假设两个线程同时调用 getInstance() 方法:
1.线程 A 和 线程 B 同时调用 getInstance()。
2.线程 A 和 线程 B 同时检查 if (instance == null),发现 instance 为 null。
3.线程 A 和 线程 B 都进入 if 块,分别创建 Singleton 实例。
4.最终,Singleton 类被创建了两次,破坏了单例模式的唯一性。
如何处理线程安全问题
为了确保线程安全,可以使用 synchronized 关键字来同步 getInstance() 方法。这样可以确保在同一时间只有一个线程可以进入 getInstance() 方法,从而避免多个线程同时创建实例。
详细解释
1.synchronized 关键字:
synchronized用于确保同一时间只有一个线程可以进入被修饰的方法或代码块。- 在
getInstance()方法上使用synchronized,可以确保在多线程环境下,只有一个线程可以执行getInstance()方法的代码块,从而避免多个线程同时创建实例。
2.线程安全的实现
当 线程 A 进入 getInstance() 方法时,其他线程(如 线程 B)会被阻塞,等待 线程 A 完成实例化。线程 B 在进入 getInstance() 方法时,instance 已经被 线程 A 创建,因此不会再次创建实例。
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
三、静态内部类实现
1.线程安全
静态内部类的加载是由JVM控制的,JVM会确保在类初始化时只有一个线程能够执行初始化操作,因此这种实现方式是线程安全的。
2.懒加载
静态内部类 SingletonHolder 只有在第一次调用 getInstance() 方法时才会被加载,从而初始化 Singleton 实例。这实现了懒加载,避免了在类加载时就初始化实例。
3.性能优化
不需要使用 synchronized 关键字来保证线程安全,因此在实例创建后,多次调用 getInstance() 方法时不会有同步开销。
4.简洁性
实现相对简洁,避免了复杂的同步逻辑。
总结:静态内部类实现单例模式是一种优雅的实现方式,它结合了饿汉式和懒汉式的优势,既保证了线程安全,又实现了懒加载,同时避免了同步带来的性能开销。推荐在需要线程安全且希望懒加载的场景中使用这种实现方式。
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
四、枚举实现
枚举实现单例模式是一种简洁且线程安全的方式,它利用了 Java 枚举类型的特性来确保单例的唯一性和线程安全性。以下是枚举实现单例模式的详细解析:
public enum Singleton {
INSTANCE;
public void doSomething() {
// 方法实现
System.out.println("Doing something...");
}
}
1.线程安全
Java 枚举类型的实例创建是线程安全的,JVM 会确保在类加载时只有一个实例被创建。
枚举的实例化过程是由 JVM 控制的,因此不需要额外的同步措施。
2.防止反序列化
枚举类型在反序列化时不会创建新的实例,因此可以防止通过反序列化破坏单例模式。
3.简洁性
枚举实现单例模式的代码非常简洁,不需要额外的 getInstance() 方法或其他复杂的逻辑。
4.唯一性
枚举类型在 Java 中是 final 的,不能被继承,因此可以确保只有一个实例存在。
public class OtherClass {
public void someMethod() {
// 获取单例实例
Singleton singleton = Singleton.INSTANCE;
// 调用单例实例的方法
singleton.doSomething();
}
}
总结:枚举实现单例模式是一种简洁、线程安全且防止反序列化的单例实现方式。它利用了 Java 枚举类型的特性,确保了单例的唯一性和线程安全性。推荐在需要线程安全且防止反序列化的场景中使用这种实现方式。
595

被折叠的 条评论
为什么被折叠?



