单例实现的几种方式

什么是单例

单例是一个只实例化一次的类,即类仅存在一个对象。

私有化构造器 + 静态成员变量

显示声明构造器,并使用 private 关键字将其私有化,避免使用 new Object 来创建对象。需要使用该实例,只需要通过成员变量 INSTANCE 访问即可。

1
2
3
4
public class MySingleton {
public static final MySingleton INSTANCE = new MySingleton();
private MySingleton(){}
}

缺点:会被 AccessibleObject.setAccessible 方法利用反射调用私有构造函数,实例化新的对象。

私有化构造器 + 静态工厂方法

该单例实现方法与上面基本一致,只需要将成员变量私有化,添加工厂方法来访问该成员变量即可。获取实例只需要调用 getInstance 即可。

1
2
3
4
5
6
7
8
public class MySingleton {
private static final MySingleton INSTANCE = new MySingleton();
private MySingleton(){}

public static MySingleton getInstance(){
return INSTANCE;
}
}

缺点:同上。

实现序列化需要注意的问题

使用上述两种方法实现单例,且需要实现可序列化时,仅仅是实现 Serializable 接口是不够的,要维护单例保证,应声明所有实例字段为 transient,并提供 readResolve 方法(Item-89)。

否则,每次反序列化实例时,都会创建一个新实例,在我们的示例中,这会导致出现虚假的 MySingleton。为了防止这种情况发生,将这个 readResolve 方法添加到 MySingleton 类中。

详细的解决方法可以查看《Effective Java》第三版 Item 89。

单元素枚举

优点:默认提供了序列化机制,提供了对多个实例化的严格保证,即使面对复杂的序列化或反射攻击也是如此。

1
2
3
public enum MySingleton{
INSTANCE;
}

缺点:当类需要扩展超类时,enum 便不在适合。

延迟加载单例(双重检查不成立?)

延迟加载即只有第一次调用时,才进行初始化。

1
2
3
4
5
6
7
8
9
10
11
public class MySingleton {
private static MySingleton INSTANCE = null;
private MySingleton(){}

public static MySingleton getInstance(){
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
}

当程序第一次调用 getInstance 方法时,MySingleton 实例才被初始化。

该延迟加载在多线程情况下,可能会创建多个实例,因此需要对 getInstance 方法进行加锁处理。

1
2
3
4
5
6
public synchronized static MySingleton getInstance(){
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}

加锁的意图在于第一次初始化的时候,保证只有一条线程进入到 if 语句中,生成实例。直接在方法上加锁,会导致性能下降。因为每次去取该实例都要去获得锁,所以需要对该方式进行优化。

1
2
3
4
5
6
7
8
9
10
public static MySingleton getInstance(){
if (INSTANCE == null) {
synchronized (MySingleton.class){
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
}
}
return INSTANCE;
}

该方法称作 双重检查 ,保证了多线程环境下,只生成一个实例,且后续对该实例的调用都不会进行加锁处理。

由于类加载以及对象初始化的特殊性值,会导致该双重检查不成立,可以使用 volatile 关键字声明变量,防止指令重排,实现最终目的。想了解该问题具体产生的原因,可以查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MySingleton {
private static volatile MySingleton INSTANCE = null;
private MySingleton(){}

public static MySingleton getInstance(){
if (INSTANCE == null) {
synchronized (MySingleton.class){
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
}
}
return INSTANCE;
}
}

一份来自 Java 巨佬关于 双重检查的阐述,点击这里


单例实现的几种方式
http://wszzf.top/2022/06/24/单例实现的几种方式/
作者
Greek
发布于
2022年6月24日
许可协议