线程的安全发布-单例模式
1、安全发布对象
发布对象:使一个对象能够被当前范围之外的代码所使用。
对象逸出:一种错误的发布。当一个对象还没有构造完成时,就使它被其他线程所见。
安全发布对象的4种方法:
- 在静态初始化函数中初始化一个对象引用。
- 将对象的引用保存到 volatile 类型域 或者 AtomicReference 对象中。
- 将对象的引用保存到某个正确构造对象的final类型域中。
- 将对象的引用保存到一个由锁保护的域中。
2、七种单例模式的写法
2.1 普通的懒汉模式
/**
* @author
* @title: SingletonEx1
* @description: 单例-懒汉模式-线程不安全
* @date 2020/4/11 15:16
*/
public class SingletonEx1 {
// 私有的构造函数
private SingletonEx1() {
// todo ……
}
// 单例对象
private static SingletonEx1 instance = null;
// 静态的工厂方法
public static SingletonEx1 getInstance () {
if (instance == null) // 多线程同时调用,这一步可能会出现问题
instance = new SingletonEx1();
return instance;
}
}
2.2 普通的饿汉模式
/**
* @author
* @title: SingletonEx2
* @description: 单例-饿汉模式-线程安全
* @date 2020/4/11 15:35
*/
public class SingletonEx2 {
// 私有的构造函数
private SingletonEx2() {
// todo ……
}
// 单例对象
private static SingletonEx2 instance =
new SingletonEx2();
public static SingletonEx2 getInstance() {
return instance;
}
}
2.3 线程安全的懒汉模式
/**
* @author
* @title: SingletonEx3
* @description: 单例-懒汉模式-线程安全-效率低
* 不推荐使用该写法,性能开销大
* @date 2020/4/11 15:37
*/
public class SingletonEx3 {
// 私有的构造函数
private SingletonEx3 () {
// todo ……
}
// 单例对象
private static SingletonEx3 instance = null;
public static synchronized SingletonEx3 getInstance() {
if (instance == null)
instance = new SingletonEx3();
return instance;
}
}
2.4 双重同步锁单例模式
/**
* @author
* @title: SingletouEx4
* @description: 单例-懒汉模式改造-双重同步锁单例模式-线程不安全-双重检测机制
* 多线程情况下,可能会由于JVM和CPU优化,导致指令重排,从而不安全。
* @date 2020/4/11 15:40
*/
public class SingletonEx4 {
// 私有的构造函数
private SingletonEx4 () {
// todo ……
}
// 单例对象
private static SingletonEx4 instance = null;
public static SingletonEx4 getInstance() {
if (instance == null) {
// 双重检测机制 B
synchronized (SingletonEx4.class) {
// 加锁
if (instance == null) // 双重检测机制
instance = new SingletonEx4(); // A-3
}
}
return instance;
}
/**
* 该写法线程不安全原因分析
* 在CPU 执行实例化 new SingletonEx4(); 的过程中,CPU 的指令顺序为:
* 1、memory = allocate() 分配对象的内存空间。
* 2、ctorInstance() 初始化对象。
* 3、instance = memory 设置 instance 指向刚分配的内存。
*
* 单线程情况下,按以上指令顺序是没有问题的,
* 在多线程情况下,JVM和CPU优化,可能会出现指令重排
*
* 出现以下顺序:
* 1、memory = allocate() 分配对象的内存空间。
* 3、instance = memory 设置 instance 指向刚分配的内存。
* 2、ctorInstance() 初始化对象。
*
* 假设现在有两个线程 A 和 B
*
* 现在 线程A走到程序第 21 行 cpu执行第3指令,
* 同时,线程B走到程序第 18 行,会判断 instance != null ,直接返回 instance.
* 而实际上 cpu 还没有执行 《ctorInstance() 初始化对象。》指令
* 从而,出现线程不安全的情况。
*
*/
}
2.5 volatile + 双重同步锁单例模式
/**
* @author
* @title: SingletonEx5
* @description: 单例-懒汉模式-线程安全
* @date 2020/4/11 15:44
*/
public class SingletonEx5 {
// 私有的构造函数
private SingletonEx5 () {
// todo ……
}
// volatile 能杜绝指令重排,用于双重检测时,能保证线程安全
private static volatile SingletonEx5 instance = null;
public static SingletonEx5 getInstance() {
if (instance == null) {
synchronized (SingletonEx5.class) {
if (instance == null)
instance = new SingletonEx5();
}
}
return instance;
}
}
2.6 静态代码块的饿汉模式
/**
* @author
* @title: SingletonEx6
* @description: 单例-饿汉模式-线程安全
* @date 2020/4/11 15:47
*/
public class SingletonEx6 {
// 私有的构造函数
private SingletonEx6 () {
// todo ……
}
// 注意static 关键修饰的代码块和函数,按顺序执行。
static {
instance = new SingletonEx6();
}
// 单例对象
private static SingletonEx6 instance = null;
public static SingletonEx6 getInstance() {
return instance;
}
}
2.7 枚举模式单例(推荐写法)
/**
* @author
* @title: SingletonEx7
* @description: 单例-枚举模式-线程安全-推荐使用该写法-效率高
* 比懒汉模式安全,比饿汉模式效率高
* @date 2020/4/11 15:51
*/
public class SingletonEx7 {
// 私有构造函数
private SingletonEx7 () {
// todo ……
}
public static SingletonEx7 getInstance() {
return Singleton.INSTANCE.getInstance();
}
private enum Singleton {
INSTANCE;
private SingletonEx7 instance;
// JVM 保证这个方法绝对只调用一次
Singleton () {
instance = new SingletonEx7();
}
public SingletonEx7 getInstance() {
return instance;
}
}
}