写点什么

7 种创建方式,带你理解 Java 的单例模式

  • 2023-08-18
    广东
  • 本文字数:3230 字

    阅读完需:约 11 分钟

7种创建方式,带你理解Java的单例模式

本文分享自华为云社区《《Java极简设计模式》第01章:单例模式(Singleton)》,作者:冰 河。

单例设计模式


看几个单例对象的示例代码,其中有些代码是线程安全的,有些则不是线程安全的,需要大家细细品味,这些代码也是在高并发环境下测试验证过的。


  • 代码一:SingletonExample1


这个类是懒汉模式,并且是线程不安全的


package io.binghe.concurrency.example.singleton;
/**
* @author binghe
* @version 1.0.0
* @description 懒汉模式,单例实例在第一次使用的时候进行创建,这个类是线程不安全的
*/
public class SingletonExample1 {
private SingletonExample1(){}
private static SingletonExample1 instance = null;
public static SingletonExample1 getInstance(){
//多个线程同时调用,可能会创建多个对象
if (instance == null){
instance = new SingletonExample1();
}
return instance;
}
}
复制代码


  • 代码二:SingletonExample2


饿汉模式,单例实例在类装载的时候进行创建,是线程安全的


package io.binghe.concurrency.example.singleton;
/**
* @author binghe
* @version 1.0.0
* @description 饿汉模式,单例实例在类装载的时候进行创建,是线程安全的
*/
public class SingletonExample2 {
private SingletonExample2(){}
private static SingletonExample2 instance = new SingletonExample2();
public static SingletonExample2 getInstance(){
return instance;
}
}
复制代码


  • 代码三:SingletonExample3


懒汉模式,单例实例在第一次使用的时候进行创建,这个类是线程安全的,但是这个写法不推荐


package io.binghe.concurrency.example.singleton;
/**
* @author binghe
* @version 1.0.0
* @description 懒汉模式,单例实例在第一次使用的时候进行创建,这个类是线程安全的,但是这个写法不推荐
*/
public class SingletonExample3 {
private SingletonExample3(){}
private static SingletonExample3 instance = null;
public static synchronized SingletonExample3 getInstance(){
if (instance == null){
instance = new SingletonExample3();
}
return instance;
}
}
复制代码


  • 代码四:SingletonExample4


懒汉模式(双重锁同步锁单例模式),单例实例在第一次使用的时候进行创建,但是,这个类不是线程安全的!!!!!


package io.binghe.concurrency.example.singleton;
/**
* @author binghe
* @version 1.0.0
* @description 懒汉模式(双重锁同步锁单例模式)
* 单例实例在第一次使用的时候进行创建,这个类不是线程安全的
*/
public class SingletonExample4 {
private SingletonExample4(){}
private static SingletonExample4 instance = null;
//线程不安全
//当执行instance = new SingletonExample4();这行代码时,CPU会执行如下指令:
//1.memory = allocate() 分配对象的内存空间
//2.ctorInstance() 初始化对象
//3.instance = memory 设置instance指向刚分配的内存
//单纯执行以上三步没啥问题,但是在多线程情况下,可能会发生指令重排序。
// 指令重排序对单线程没有影响,单线程下CPU可以按照顺序执行以上三个步骤,但是在多线程下,如果发生了指令重排序,则会打乱上面的三个步骤。
//如果发生了JVM和CPU优化,发生重排序时,可能会按照下面的顺序执行:
//1.memory = allocate() 分配对象的内存空间
//3.instance = memory 设置instance指向刚分配的内存
//2.ctorInstance() 初始化对象
//假设目前有两个线程A和B同时执行getInstance()方法,A线程执行到instance = new SingletonExample4(); B线程刚执行到第一个 if (instance == null){处,
//如果按照1.3.2的顺序,假设线程A执行到3.instance = memory 设置instance指向刚分配的内存,此时,线程B判断instance已经有值,就会直接return instance;
//而实际上,线程A还未执行2.ctorInstance() 初始化对象,也就是说线程B拿到的instance对象还未进行初始化,这个未初始化的instance对象一旦被线程B使用,就会出现问题。
public static SingletonExample4 getInstance(){
if (instance == null){
synchronized (SingletonExample4.class){
if(instance == null){
instance = new SingletonExample4();
}
}
}
return instance;
}
}
复制代码


线程不安全分析如下:


当执行 instance = new SingletonExample4();这行代码时,CPU 会执行如下指令:


1.memory = allocate() 分配对象的内存空间


2.ctorInstance() 初始化对象


3.instance = memory 设置 instance 指向刚分配的内存


单纯执行以上三步没啥问题,但是在多线程情况下,可能会发生指令重排序。


指令重排序对单线程没有影响,单线程下 CPU 可以按照顺序执行以上三个步骤,但是在多线程下,如果发生了指令重排序,则会打乱上面的三个步骤。


如果发生了 JVM 和 CPU 优化,发生重排序时,可能会按照下面的顺序执行:


1.memory = allocate() 分配对象的内存空间


3.instance = memory 设置 instance 指向刚分配的内存


2.ctorInstance() 初始化对象


假设目前有两个线程 A 和 B 同时执行 getInstance()方法,A 线程执行到 instance = new SingletonExample4(); B 线程刚执行到第一个 if (instance == null){处,如果按照 1.3.2 的顺序,假设线程 A 执行到 3.instance = memory 设置 instance 指向刚分配的内存,此时,线程 B 判断 instance 已经有值,就会直接 return instance;而实际上,线程 A 还未执行 2.ctorInstance() 初始化对象,也就是说线程 B 拿到的 instance 对象还未进行初始化,这个未初始化的 instance 对象一旦被线程 B 使用,就会出现问题。


  • 代码五:SingletonExample5


懒汉模式(双重锁同步锁单例模式)单例实例在第一次使用的时候进行创建,这个类是线程安全的,使用的是 volatile + 双重检测机制来禁止指令重排达到线程安全


package io.binghe.concurrency.example.singleton;
/**
* @author binghe
* @version 1.0.0
* @description 懒汉模式(双重锁同步锁单例模式)
* 单例实例在第一次使用的时候进行创建,这个类是线程安全的
*/
public class SingletonExample5 {
private SingletonExample5(){}
//单例对象 volatile + 双重检测机制来禁止指令重排
private volatile static SingletonExample5 instance = null;
public static SingletonExample5 getInstance(){
if (instance == null){
synchronized (SingletonExample5.class){
if(instance == null){
instance = new SingletonExample5();
}
}
}
return instance;
}
}
复制代码


  • 代码六:SingletonExample6


饿汉模式,单例实例在类装载的时候(使用静态代码块)进行创建,是线程安全的


package io.binghe.concurrency.example.singleton;
/**
* @author binghe
* @version 1.0.0
* @description 饿汉模式,单例实例在类装载的时候进行创建,是线程安全的
*/
public class SingletonExample6 {
private SingletonExample6(){}
private static SingletonExample6 instance = null;
static {
instance = new SingletonExample6();
}
public static SingletonExample6 getInstance(){
return instance;
}
}
复制代码


  • 代码七:SingletonExample7


枚举方式进行实例化,是线程安全的,此种方式也是线程最安全的


package io.binghe.concurrency.example.singleton;
/**
* @author binghe
* @version 1.0.0
* @description 枚举方式进行实例化,是线程安全的,此种方式也是线程最安全的
*/
public class SingletonExample7 {
private SingletonExample7(){}
public static SingletonExample7 getInstance(){
return Singleton.INSTANCE.getInstance();
}
private enum Singleton{
INSTANCE;
private SingletonExample7 singleton;
//JVM保证这个方法绝对只调用一次
Singleton(){
singleton = new SingletonExample7();
}
public SingletonExample7 getInstance(){
return singleton;
}
}
}
复制代码


点击关注,第一时间了解华为云新鲜技术~

发布于: 刚刚阅读数: 3
用户头像

提供全面深入的云计算技术干货 2020-07-14 加入

生于云,长于云,让开发者成为决定性力量

评论

发布
暂无评论
7种创建方式,带你理解Java的单例模式_Java_华为云开发者联盟_InfoQ写作社区