首页 > 编程语言 >7种创建方式,带你理解Java的单例模式

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

时间:2023-08-18 10:36:40浏览次数:39  
标签:Java 创建 private instance 线程 单例 static public

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

今天给大家介绍Java极简设计模式中的单例设计模式(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;
        }
    }
}

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

标签:Java,创建,private,instance,线程,单例,static,public
From: https://blog.51cto.com/u_15214399/7132209

相关文章

  • Java学习笔记(十三)
    7.6 枚举类1、什么是枚举类?枚举类是指一种特殊的类,这种类的对象只有有限的固定的几个常量对象。2、什么情况会用枚举类呢?例如:Month类,Week类等等,他们的对象应该是固定的有限的几个。Month类:12个对象Week类:7个对象Season(季节)类:4个对象3、如何声明枚举类呢?在JDK1.5之前:(1......
  • java线程死锁怎么处理
    在Java中,线程死锁是指两个或多个线程被阻塞,因为它们互相等待对方释放资源。这种情况下,线程将永远无法继续执行下去。处理线程死锁的方法之一是使用以下步骤:1.分析死锁:确定哪些线程和资源参与了死锁,并找出造成死锁的原因。你可以使用工具如线程转储分析工具(ThreadDumpAnalyzer)或......
  • 【Java技术专题】「入门到精通系列教程」深入探索Java特性中并发编程体系的原理和实战
    并发编程介绍当今软件开发领域越来越强调性能和伸缩性,因此,利用并发编程可以更好地利用计算资源,提高应用程序的性能和响应速度。以下是一些关于并发编程的相关信息供您参考。什么是并发编程并发编程是指在一个程序中同时执行多个独立的计算任务,并通过各种手段来协调不同任务之间的交......
  • 【Spring boot】快速创建springboot项目
     1、文件-新建项目 2、选择spring填写信息 3、添加依赖包<后续可以在pom.xml中引入依赖包>  4、项目生成完成  ......
  • [Microsoft Azure] 在 Azure App Service 上创建和使用虚拟目录
    本文将详细介绍如何在MicrosoftAzure的AzureAppService上创建和使用虚拟目录。我们将通过实际操作步骤来帮助您理解虚拟目录的创建和使用方法,为您的应用程序提供灵活的资源管理方式。一、什么是虚拟目录?虚拟目录是一种将物理文件夹路径映射到Web应用程序的URL空间......
  • QtWebChannel和JavaScript进行通信(简单理解)
    说明在使用Qt(C++)和JavaScript之间实现通信时,通常会使用一些模块和技术来使两者能够交互和传递数据。这种通信通常用于在Qt应用程序中嵌入Web内容,或者在Web页面中嵌入Qt应用程序。以下是一些常用的模块和技术,以及它们的作用QtWebEngine模块:作用:QtWebEngine是Qt中的Web引擎,允......
  • Java 子父类型集合之间的转换
    假设现在有这样一个方法,入参是父类型的集合参数,这是个通用方法,你需要共用它,你现在要传子类型集合进去,怎么办?classAnimal{}classDogextendsAnimal{}publicclassService{publicvoiddoSth(){List<Animal>animals=newArrayList<>();han......
  • java 每天23点定时删除某个Folder下的文件
    importjava.io.IOException;importjava.nio.file.*;importjava.time.LocalDate;importjava.time.LocalDateTime;importjava.time.LocalTime;importjava.util.concurrent.Executors;importjava.util.concurrent.ScheduledExecutorService;importjava.util.concu......
  • Java日志系列:日志门面JCL、SLF4J
    目录一、日志门面说明二、JCL使用JCL入门JCL原理三、SLF4J使用配合自身简单日志实现(slf4j-simple)配置logback日志实现配置Log4J日志实现(需适配器)配置JUL日志实现(需适配器)添加slf4j-nop依赖(日志开关)桥接旧的日志实现框架一、日志门面说明当我们的系统变的更加复杂的......
  • 创建UserDetailsService实现类
    packagecom.security.service.impl;importcom.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;importcom.security.domain.LoginUser;importcom.security.domain.User;importcom.security.mapper.UserMapper;importorg.springframework.beans.fa......