饿汉式、懒汉式单例模式实现,懒汉式单例模式解决线程安全问题。
1. 单例模式
单例模式是保证只能创建一个类的实例,达到节约内存方便管理的目的。
单例模式可分为两种:
饿汉式:
在获取单例对象之前完成单例的创建;
缺点:在刚开始时就要加载一些复杂的单例实现导致性能的降低。
懒汉式:
真正需要时进行单例的创建;
缺点:延迟加载提升性能;但根据写法不同可能会导致线程不安全的问题。
2. 饿汉式单例(静态常量)
1 | public class Singleton { |
3. 懒汉式单例
懒汉式1(线程不安全)
1
2
3
4
5
6
7
8
9
10
11
12public class Singleton {
private static Singleton INSTANCE;
private Singleton() {
}
public static Singleton getInstance() {
// 当两个线程都到达此处,都会判断当前为空;线程不安全
if(INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
懒汉式2(synchronized)
使用synchronized关键字修饰方法,线程安全,但性能较差,并发下只能有一个线程进入获取单例对象。
1
2
3
4
5
6
7
8
9
10
11public class Singleton {
private static Singleton INSTANCE;
private Singleton() {
}
public synchronized static Singleton getInstance() {
if(INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
懒汉式3(synchronized优化)
将synchronized关键字移到方法内部,使得多个线程都能进入方法,提升性能,但会导致线程不安全。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class Singleton {
private static Singleton INSTANCE;
private Singleton() {
}
public static Singleton getInstance() {
if(INSTANCE == null) {
// 并发下使多个线程都能进入方法里面;
// 但多个线程判断为null导致线程不安全
synchronized (Singleton.class) {
INSTANCE = new Singleton();
}
}
return INSTANCE;
}
}
懒汉式4(volatile双重检查)
最终做法:双重检查机制+volatile关键字。
二次检查机制后为什么需要加上volatile关键字?
new Singleton()
是一个非原子操作。编译器为了编译性能可能会进行指令重排序。线程A执行创建单例时,需要分配内存空间->调用构造器初始化实例->返回地址给引用
,构造函数可能在对象初始化完成前执行完毕然后赋值。实际是返回一个未初始化好的对象,可能会导致NPE异常。volatile保证可见性
一旦线程A完成单例的创建,其他线程可立即知道INSTANCE不为null;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class Singleton {
private volatile static Singleton INSTANCE;
private Singleton() {
}
public static Singleton getInstance() {
// 第一次检查
if(INSTANCE == null) {
// 多个线程进入方法,此处可能都判断为空
synchronized (Singleton.class) { // 单进程进入
// 第二次检查
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
懒汉式5(静态内部类)
jvm在类的初始化阶段(即在Class被加载后,且线程使用之前)会执行类的初始化,在执行初始化期间,jvm会去获取一个锁,这个锁可以同步多个线程对同一个类的初始化。
和volatile双重检查方案的对比:
静态内部类的实现更加简洁,
但volatile双重检查方案有一个额外的优势:除了可以对静态字段实现延迟初始化之外,还可以对非静态字段实现初始化。而静态内部类只能对外部类的静态字段初始化。
1
2
3
4
5
6
7
8
9
10public class Singleton {
private Singleton() {
}
private static class SingletonInstace {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstace.INSTANCE;
}
}扩展:枚举实现单例,枚举实际是一个多例模式,直接定义一个实例时就相当于单例。但不够灵活。
4. volatile和synchronized的区别:
- volatile只能修饰实例变量和类变量,而synchronized可以修饰方法及代码块;
- volatile保证数据的可见性,任何线程对它的修改立即对其他线程可见。但不保证原子性(多线程写不保证线程安全);而synchronized是一种互斥机制
- volatile用于禁止指令重排序,可以解决单例双重检查对象初始化代码执行乱序问题。
- volatile(因为无锁)可以看做是轻量版的synchronized。如果是对一个共享变量进行多线程的赋值而没有其他操作,可以用volatile代替synchronized,因为赋值本身是具有原子性的,而volatile又保证其可见性,所以线程安全。
- 本文作者: zicair
- 本文链接: https://zicair.github.io/2020/06/12/单例模式/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!