动机
在软件系统中有些特殊的类,必须保证只有一个实例,才能保证逻辑正确性以及良好的效率。如何绕过常规的构造器,来提供一种机制保证一个类只有一个实例。定义
保证一个类仅有一个实例,并提供一个该实例的全局访问点。懒汉模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31public class Singleton1 {
private static Singleton1 instance = null;
private Singleton1() {
}
// 1、适用于单线程环境(不推荐)
public static Singleton1 getInstanceA() {
if (null == instance) {
// 用到才构造,懒汉模式
instance = new Singleton1();
}
return instance;
}
// 2、适用于多线程环境,但效率不高(不推荐)
public static synchronized Singleton1 getInstanceB() {
if (instance == null) {
instance = new Singleton1();
}
return instance;
}
// 3、双重检查加锁,重排序会导致问题
public static Singleton1 getInstanceC() {
if (instance == null) {
synchronized (Singleton1.class) {
if (instance == null) {
instance = new Singleton1();
}
}
}
return instance;
}
}实现3逻辑上正确,但是由于编译器指令重排导致不安全(某一线程可能拿到一个不等于nullptr但是没有执行构造函数的 instance ),在Java平台可以对 instance 加上 volatile 使得整个赋值过程不能reorder;
1
2
3
4
5
6
7
8
9public class Singleton{
private static class SingletonHolder{
public static Singleton instance = new Singleton();
}
private Singleton(){}
public static Singleton newInstance(){
return SingletonHolder.instance;
}
}利用了类加载机制来保证只创建一个instance实例。与饿汉模式一样,不存在多线程并发的问题。
在应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式的延迟加载。
也就是说这种方式可以同时保证延迟加载和线程安全。reorder
instance=new Singleton();
这行代码逻辑上是先分配内存,再调用构造函数,再将内存地址复制给变量名;重排之后可能先申请内存,然后就复制给名称,最后再调用构造函数。饿汉模式
eg. singleton in Spring-AOP1
2
3
4
5
6
7
8
9
10
11
12public final class GlobalAdvisorAdapterRegistry {
private GlobalAdvisorAdapterRegistry() {
}
// 事先构造好,饿汉模式
private static AdvisorAdapterRegistry instance = new DefaultAdvisorAdapterRegistry();
public static AdvisorAdapterRegistry getInstance() {
return instance;
}
static void reset() {
instance = new DefaultAdvisorAdapterRegistry();
}
}总结
- 饿汉模式线程安全,但是一开始就初始化,没有用到时造成浪费,适合内存占用小且一定会使用的情况。
- 实现线程安全的singleton,注意检查双检查锁的正确实现。
- 不使用锁和双重检查,最好使用内部类的方式。
- 另外基于枚举实现单例能解决序列化的问题。
赏
使用支付宝打赏
使用微信打赏
若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏
扫描二维码,分享此文章