单例模式

# 单例模式

  • 构造器私有
  • 自行创建实例
  • 对外提供实例

饿汉式:类加载时初始化。 懒汉式:延迟初始化,需要考虑并发问题。

提示

私有化构造器并不保险。因为它抵御不了反射攻击。

# 常用创建方式

# 双重锁检查(Double Check Lock)

public class Singleton {  
    private static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
        //如果单例已经存在,那么就不再需要进行同步操作,而是直接返回这个实例,如果没有创建,才会进入同步块
        if (singleton == null) {  
            //同步块的目的与之前相同,目的是为了防止有两个调用同时进行时,导致生成多个实例,有了同步块,每次只能有一个线程调用能访问同步块内容,当第一个抢到锁的调用获取了实例之后,这个实例就会被创建,之后的所有调用都不会进入同步块,直接在第一重判断就返回了单例
            synchronized (Singleton.class) {  
                //为了进入synchronized块后只有对象为null的情况下才创建实例,避免重复创建对象实例,而且synchronized块锁住的是类的Class对象,保证了在多线程环境下只有一个线程进入synchronized块
                if (singleton == null) {  
                    singleton = new Singleton();  
                }  
            }  
        }  
        return singleton;  
    }  
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

少数情况下DCL还是会出问题,因为语句singleton = new Singleton();分为三步:

  1. 在Java堆中分配内存空间
  2. 进行Singleton初始化
  3. 将singlten指向分配的内存空间

某些JVM会把这3个步骤进行指令重排序,变为以下顺序:

  1. 在java堆分配中Singleton对象的内存空间
  2. 把singleton指向1步骤中分配的内存空间
  3. 进行Singleton类的初始化过程.

如果在单线程环境是没有问题的,因为即使发生指令重排,在getSingtleton方法返回的时候,singleton指向的对象已经完成了初始化。但是在多线程环境中,当第一个线程先执行的1、3步骤,此时singtleton已不为null,但还未初始化。当第二个线程进入getSingleton,判断singleton==nullfalse直接return,那么就拿到了一个还未初始化完毕的对象,这样就可能导致程序崩溃。

针对这种问题,可以使用volatile来禁止指令重排序

public class Singleton {  
    //加上Volatile关键字修饰
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
        //如果单例已经存在,那么就不再需要进行同步操作,而是直接返回这个实例,如果没有创建,才会进入同步块
        if (singleton == null) {  
            //同步块的目的与之前相同,目的是为了防止有两个调用同时进行时,导致生成多个实例,有了同步块,每次只能有一个线程调用能访问同步块内容,当第一个抢到锁的调用获取了实例之后,这个实例就会被创建,之后的所有调用都不会进入同步块,直接在第一重判断就返回了单例
            synchronized (Singleton.class) {  
                //为了进入synchronized块后只有对象为null的情况下才创建实例,避免重复创建对象实例,而且synchronized块锁住的是类的Class对象,保证了在多线程环境下只有一个线程进入synchronized块
                if (singleton == null) {  
                    singleton = new Singleton();  
                }  
            }  
        }  
        return singleton;  
    }  
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

也可以通过局部变量作为缓冲

public class Singleton {  
    private static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
        //1、增加一个局部变量,同为Singleton类型
        Singleton singletonVar = null;
 
        //如果单例已经存在,那么就不再需要进行同步操作,而是直接返回这个实例,如果没有创建,才会进入同步块
        if (singleton == null) {  
            //同步块的目的与之前相同,目的是为了防止有两个调用同时进行时,导致生成多个实例,有了同步块,每次只能有一个线程调用能访问同步块内容,当第一个抢到锁的调用获取了实例之后,这个实例就会被创建,之后的所有调用都不会进入同步块,直接在第一重判断就返回了单例
            synchronized (Singleton.class) {  
                //为了进入synchronized块后只有对象为null的情况下才创建实例,避免重复创建对象实例,而且synchronized块锁住的是类的Class对象,保证了在多线程环境下只有一个线程进入synchronized块
                if (singleton == null) {  
 
                    //2、执行实例时,先实例化这个局部变量
                    singletonVar = new Singleton();  
                    //3、待局部变量实例化完毕后,才把这个实例赋值给要返回的静态变量singleton
                    singleton = singletonVar;
                }  
            }  
        }  
        return singleton;  
    }  
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

需要配合volatile内存可见性

# 静态内部类

利用类加载机制,类只有在被用到时才会被加载,静态属性在类加载的时候会被创建。

public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
    return SingletonHolder.INSTANCE;  
    }  
}
1
2
3
4
5
6
7
8
9

# 枚举方式

这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。 这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。 不能通过 reflection attack 来调用私有构造方法。

public class Singleton {
    private Singleton(){
    }   
    public static enum SingletonEnum {
        SINGLETON;
        private Singleton instance = null;
        private SingletonEnum(){
            instance = new Singleton();
        }
        public Singleton getInstance(){
            return instance;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
上次更新: 2023/12/28, 14:40:19
最近更新
01
go-admin-ui项目仿写练手1-登录页
06-29
02
maven依赖问题
06-17
03
JVM相关命令
02-21
更多文章>