零、 本文纲要
- 一、 单例模式
- 饿汉式
① 静态变量方式
② 静态代码块
③ 枚举 - 懒汉式
① 静态方法(线程不安全)
② 静态synchronized方法(线程安全)
③ 静态方法双检锁(线程不安全)
④ 静态方法双检锁volatile优化(线程安全)
⑤ 静态内部类(线程安全)
- 二、 单例模式问题解决
- 防止序列化破坏单例
- 防止反射破坏单例
- 三、 JDK中存在的单例模式
java.lang.Runtime类
一、 单例模式
1. 饿汉式
- ① 静态变量方式
Ⅰ 私有构造private Singleton() {}
Ⅱ 静态变量private static Singleton instance = new Singleton();
Ⅲ 静态方法获取单例public static Singleton getInstance() { return instance; }
/**
* 饿汉式
* 静态变量创建类的对象
*/
public class Singleton {
//私有构造方法
private Singleton() {}
//在成员位置创建该类的对象
private static Singleton instance = new Singleton();
//对外提供静态方法获取该对象
public static Singleton getInstance() {
return instance;
}
}
- ② 静态代码块
Ⅰ 私有构造
Ⅱ 静态变量
Ⅲ 静态代码块赋值
Ⅳ 静态方法获取对象
/**
* 饿汉式
* 在静态代码块中创建该类对象
*/
public class Singleton {
//私有构造方法
private Singleton() {}
//在成员位置创建该类的对象
private static Singleton instance;
static {
instance = new Singleton();
}
//对外提供静态方法获取该对象
public static Singleton getInstance() {
return instance;
}
}
- ③ 枚举
/**
* 枚举方式
*/
public enum Singleton {
INSTANCE;
}
2. 懒汉式
- ① 静态方法(线程不安全)
/**
* 懒汉式
* 线程不安全
*/
public class Singleton {
//私有构造方法
private Singleton() {}
//在成员位置创建该类的对象
private static Singleton instance;
//对外提供静态方法获取该对象
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
存在问题:
Ⅰ 多线程下单例难以保证
sequenceDiagram
participant t1 as Thread1
participant t2 as Thread2
t1->t1:instance == null
t1->>+t2:Thread1时间片耗尽,Thread2使用CPU
t2->t2:instance == null<br>instance = new Singleton()<br>return instance
t2->>-t1:Thread2使用完CPU,Thread1分配使用
t1->t1:instance = new Singleton()<br>return instance
静态方法并发问题.png
上图中不难看出,由于上下文切换的原因,本应单例的实例被创建了多次。
Ⅱ 多线程下安全问题
6: new #3 // class com/stone/Singleton
9: dup
10: invokespecial #4 // Method "<init>":()V
13: putstatic #2 // Field instance:Lcom/stone/Singleton;
new指令
:在java堆上为com/stone/Singleton对象分配内存空间,并将地址压入操作数栈顶;
dup指令
:复制操作数栈顶值,并将其压入栈顶,此时操作数栈上有连续相同的两个对象地址;
invokespecial指令
:需要从操作数栈顶弹出一个this引用,来调用实例初始化方法"<init>":()V
,这一步会弹出一个之前入栈的对象地址;
putstatic指令
:将对象地址赋值给静态变量Singleton instance。
sequenceDiagram
participant t1 as Thread1
participant t2 as Thread2
t1->t1:new<br>dup<br>putstatic
t1->>+t2:Thread1时间片耗尽,Thread2使用CPU
t2->t2:instance == null不成立<br>直接返回instance
t2->>-t1:Thread2使用完CPU,Thread1分配使用
t1->t1:invokespecial
指令重排异常.png
由于发生了指令重排,instance未完成构造但被返回,故而出错。
- ② 静态synchronized方法(线程安全)
/**
* 懒汉式
* 线程安全
*/
public class Singleton {
//私有构造方法
private Singleton() {}
//在成员位置创建该类的对象
private static Singleton instance;
//对外提供静态方法获取该对象
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
由于synchronized是添加在方法上,多线程并发时会有争抢,性能有损耗。
- ③ 静态方法双检锁(线程不安全)
/**
* 双重检查方式
*/
public class Singleton {
//私有构造方法
private Singleton() {}
private static Singleton instance;
//对外提供静态方法获取该对象
public static Singleton getInstance() {
//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
if(instance == null) {
synchronized (Singleton.class) {
//抢到锁之后再次判断是否为null
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
synchronized关键字保证了原子性、可见性、有序性。
但是这里的有序性是保证acquire barrier到release barrier内的指令不能和外部的指令重排,即我们monitor所管理的指令与外部指令不发生重排。
10: monitorenter
11: getstatic #2 // Field instance:Lcom/stone/Singleton;
14: ifnonnull 27
17: new #3 // class com/stone/Singleton
20: dup
21: invokespecial #4 // Method "<init>":()V
24: putstatic #2 // Field instance:Lcom/stone/Singleton;
27: aload_0
28: monitorexit
29: goto 37
32: astore_1
33: aload_0
34: monitorexit
所以,其内部指令还是存在重排的可能。则会与静态方法(线程不安全)
的案例发生一样的线程安全问题,出现空指针的异常。
- ④ 静态方法双检锁volatile优化(线程安全)
/**
* 双重检查方式
*/
public class Singleton {
//私有构造方法
private Singleton() {}
private static volatile Singleton instance;
//对外提供静态方法获取该对象
public static Singleton getInstance() {
//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实际
if(instance == null) {
synchronized (Singleton.class) {
//抢到锁之后再次判断是否为空
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
当Thread1执行到volatile关键字修饰的instance变量的操作时,此处为instance = new Singleton();
操作,则会在该操作上方添加一个写屏障,该操作下方添加一个读屏障。写屏障保证操作上方的写操作不能越过屏障重排,读屏障保证操作下方的读操作不能越过屏障重排。则Thread2要么读到null竞争锁,要么等instance = new Singleton();
操作完成,读到生成的instance对象。
- ⑤ 静态内部类(线程安全)
/**
* 静态内部类方式
*/
public class Singleton {
//私有构造方法
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
//对外提供静态方法获取该对象
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
二、 单例模式问题解决
1. 防止序列化破坏单例
重写readResolve方法,如下:
public class Singleton implements Serializable {
//私有构造方法
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
//对外提供静态方法获取该对象
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* 下面是为了解决序列化反序列化破解单例模式
*/
private Object readResolve() {
return SingletonHolder.INSTANCE;
}
}
2. 防止反射破坏单例
在构造方法内做非空判断,如下:
public class Singleton {
//私有构造方法
private Singleton() {
/*
反射破解单例模式需要添加的代码
*/
if(instance != null) {
throw new RuntimeException();
}
}
private static volatile Singleton instance;
//对外提供静态方法获取该对象
public static Singleton getInstance() {
if(instance != null) {
return instance;
}
synchronized (Singleton.class) {
if(instance != null) {
return instance;
}
instance = new Singleton();
return instance;
}
}
}
三、 JDK中存在的单例模式
-
java.lang.Runtime
类
Ⅰ 私有构造
Ⅱ 静态变量
Ⅲ 静态方法获取
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
... ...
}
四、 结尾
以上即为设计模式-创建者模式-单例模式的全部内容,感谢阅读。
网友评论