在面向对象编程中,单例指的是一个类只允许创建一个实例,并且提供一个全局访问的接口。单例模式主要解决的问题是在多线程环境下,如何保证一个类只有一个实例,并且能够在全局范围内访问该实例。懒汉式单例模式是其中一种最为常见的实现方式。
一、基本定义
懒汉式单例模式也称为“懒加载”或“延迟初始化”,其主要思想是:只有当第一次请求实例时,才会实例化对象,避免了类在初始化时就实例化对象,从而影响应用的启动效率。
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
在上面的代码中,instance
是 Singleton
类的一个私有静态变量,用于存储该类的唯一实例。getInstance()
方法是获取该实例的唯一接口。在每次调用 getInstance()
方法时,都会先判断 instance
是否为空,如果为空,则创建一个新的实例,否则直接返回已存在的实例。
二、线程安全
由于多线程环境下有可能出现竞态条件,从而导致两个线程同时执行到 instance == null
的判定语句,从而实例化两个实例,所以懒汉式单例模式的线程安全性较差。
为了避免这种问题,可以采用多种方式对 getInstance()
方法进行线程安全的设计。下面介绍三种常用的线程安全方案:
1. synchronized关键字
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
通过在 getInstance()
方法上添加 synchronized
关键字,可以将该方法变为同步方法,从而保证了只有一个线程能够同时进入该方法进行实例化。但是由于每次调用该方法都需要获得锁,所以会造成较大的性能损耗。
2. Double Check Lock(双重校验锁)
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
在 Double Check Lock 方案中,同样是通过使用 synchronized
关键字对 instance
的实例化进行加锁,但是只有在 instance
为 null
时,才会进入 synchronized
块进行实例化。这种方式既保证了线程安全性,又可以避免多次获取锁造成的性能损耗。
3. 静态内部类
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
在静态内部类方案中,Singleton
类中的 instance
被声明为私有,并且新建了一个静态内部类 SingletonHolder
,在 SingletonHolder
类中声明了一个静态的、final 的、并且是 Singleton
类型的变量 INSTANCE
。在 getInstance()
方法中,直接返回 SingletonHolder.INSTANCE
,从而保证了线程安全性,同时也可以达到懒加载的效果。
三、序列化与反序列化
在使用单例模式时,有时需要对单例对象进行序列化或反序列化,但是由于单例类的构造函数被私有化,并且 getInstance()
方法是静态方法,所以需要对 Singleton
类进行特殊处理才能保证序列化的正确性。以下是一种序列化与反序列化的方式:
public class Singleton implements Serializable {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
private Object readResolve() throws ObjectStreamException {
return instance;
}
}
在上述代码中,我们将 instance
变量声明为静态变量,并在定义时就实例化了对象。为了保证反序列化时不会创建新的实例,我们定义了一个 readResolve()
方法,将反序列化返回的对象直接指向 instance
,从而保证了单例的正确性。
四、反射
在 Java 的反射机制中,可以通过调用构造函数的 newInstance()
方法来创建一个类的实例。对于懒汉式单例模式而言,如果想要通过反射来创建新的实例,则需要对代码进行特殊的设计。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
if (instance != null) {
throw new IllegalStateException("Already initialized.");
}
}
public static Singleton getInstance() {
return instance;
}
private Object readResolve() throws ObjectStreamException {
return instance;
}
}
在上面的代码中,我们在 Singleton
类的私有构造函数中加入了一个判断语句,判断 instance
是否为 null
。如果 instance
已经被实例化,则抛出 IllegalStateException
异常,从而避免通过反射创建新的实例。
五、结论
懒汉式单例模式是一种实现单例模式的常见方式,其主要思想是在需要时才进行实例化。但是由于多线程环境下可能存在线程安全问题,需要进行特殊的处理。此外,序列化与反序列化以及反射都可能对懒汉式单例模式带来一些问题,需要进行特殊的设计和处理。