Java设计模式之单例模式详细讲解

news2024/11/29 12:51:39

设计模式与单例模式

1、什么是单例模式

​ 单例模式是指保证某个类在整个软件系统中只有一个对象实例,并且该类仅提供一个返回其对象实例的方法(通常为静态方法)

2、单例模式的种类

​ 经典的单例模式实现方式一般有五种

2.1 饿汉式

// ①饿汉式:使用静态常量
static class Singleton {
    // 1.构造器私有化,其他类不能new
    private Singleton() {}
    // 2.类的内部创建对象
    private final static Singleton instance = new Singleton();
    // 3.向外部暴露一个静态的公共方法
    public static Singleton getInstance() {
        return instance;
    }
}
// ②饿汉式:使用静态代码块
static class Singleton {
    // 1.构造器私有化,其他类不能new
    private Singleton() {}
    private static final Singleton instance;
    // 2.静态代码块实例化
    static {
        instance = new Singleton();
    }
    // 3.向外部暴露一个静态的公共方法
    public static Singleton getInstance() {
        return instance;
    }
}

​ 饿汉式顾名思义就是迫不及待地加载该类的对象实例,对象实例的加载最早是在类的加载过程中的初始化阶段(即静态引用变量的加载,对应字节码文件中方法的执行),加载过程由JVM保证线程安全。饿汉式会浪费内存,但是随着计算机的发展,内存已经不是问题了,所以使用饿汉式也未尝不可。

JDK源码举例:
在这里插入图片描述
​ 该类位于java.lang包下,首先将构造方法私有化,声明了一个私有的静态变量并且对该变量进行对象实例的创建,再创建一个公有的静态方法返回这个对象实例,这是比较常用的一种实现单例模式的方式。

2.2 懒汉式

// ①懒汉式:线程不安全
static class Singleton {
    // 1.构造器私有化,其他类不能new
    private Singleton() {}
    private static Singleton instance;
    // 2.向外部暴露一个静态的公共方法
    public static Singleton getInstance() {
        // 3.instance == null时进行实例化
        if ( instance == null ) {
            // new Singleton()不是一个原子操作,JVM中会进行大致[创建对象-分配内存-对象初始化]等过程,在这之前instance都为null
            // 多线程情况下,多个线程同时执行到该位置,线程获取到时间片后会继续执行,就可能创建多个实例
            instance = new Singleton();
        }
        return instance;
    }
}
// ②懒汉式:线程安全(方法上添加 synchronized 关键字)
static class Singleton {
    // 1.构造器私有化,其他类不能new
    private Singleton() {}
    private static Singleton instance;
    // 2.向外部暴露一个静态的公共方法, synchronized 保证线程安全
    public static synchronized Singleton getInstance() {
        // 3.instance == null时进行实例化
        if ( instance == null ) {
            instance = new Singleton();
        }
        return instance;
    }
}

懒汉式就是在创建对象实例前先判断是否已经创建,但是由于对象实例的创建并不是一个原子过程,所以会出现线程安全问题,可以在方法上添加synchronized解决,当然会牺牲一定的性能。基于以上原因,不推荐使用懒汉式的方式实现单例模式。

​ 如何证明对象实例的创建不是一个原子操作?字节码指令可以从侧面证明

// Java源码
public class Test {
    public Test getTest() {
        return new Test();
    }
}

在这里插入图片描述
红框1的位置有三条字节码指令,这还只是字节码的层面,再往低层还会有更多的步骤,所以很明显对象实例的创建不是一个原子操作

2.3 双重检查锁

static class Singleton {
    // 1.构造器私有化,其他类不能new
    private Singleton() {}
    // 2.volatile保证多线程下的可见性
    private static volatile Singleton instance;
    // 3.向外部暴露一个静态的公共方法
    public static Singleton getInstance() {
        // 3.非空判断
        if ( instance == null ) {
            // 4.同步代码块
            synchronized (Singleton.class) {
                // 5.再次非空判断(保证多线程下的单例)
                if ( instance == null ) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

双重检查锁是最复杂的一种单例模式实现方式,我把它拆分成三个问题:

​ ① 为什么synchronized不加到方法上?

​ 如果添加到方法上,两次非空判断就没有必要了,一次就够了,就转化成了懒汉式(线程安全),这种方式效率不高,因为每次调用都需要获取锁和释放锁。

​ ② 为什么要做两次非空判断?

​ 之前也提到过:对象实例的创建不是一个原子操作。线程安全问题也是出在这一过程中的,解决方案就是添加synchronized关键字,但是添加到方法上效率又太低了;

​ 既然问题是出现在对象实例创建的过程中,那么只对这一段代码进行同步操作(加锁对象就是当前的Class对象,因为对象实例只有一个);

​ 第一层的非空判断是为了如果对象实例已经创建完成了,就不需要再次进入同步代码块了,直接返回创建好的对象实例即可。

​ ③ 为什么要加volatile?
在这里插入图片描述
根据对象实例创建的字节码指令可以看出对象实例的创建大致分为三步:

​ ① 在堆内存中分配对象内存

​ ② 调用方法,执行对象实例的初始化

​ ③ 将对象引用赋给静态变量

大家应该对JMM模型和happens-before有所了解,简单来说JMM模型是对编译器和处理器的约束,happens-before是对开发者的约束。

编译器和处理器在实际运行时,为了执行效率可能会对指令进行重排序的操作,虽然单线程中不会影响执行结果,但是如果是多线程就会出现问题。

像对象实例创建过程的三条指令中②③就有可能会被优化为③②,但是①一定会先执行,因为②③依赖于①,此时执行顺序为①③②,其他线程就会获取到一个未初始化的对象,导致执行出错。

而volatile关键字的语义包含两个:

​ ① 保证可见性

​ ② 禁止指令重排序(所以添加volatile后,执行顺序就是①②③了)

​ JDK源码举例:
在这里插入图片描述
该类是位于java.lang包下的System类,经典的双重检查锁实现方式。

2.4 静态内部类

static class Singleton {
    // 1.构造器私有化,其他类不能new
    private Singleton() {}
    // 2.静态内部类,Singleton类加载的时候不会加载内部类,只有用到内部类时才回去加载内部类(保证懒加载)
    private static class SingletonInstance {
        private static final Singleton instance = new Singleton();
    }
    // 3.向外部暴露一个静态的公共方法,此时会装载SingletonInstance,类装载时是线程安全的(保证线程安全)
    public static Singleton getInstance() {
        return SingletonInstance.instance;
    }
}

​ 这是一种很巧妙的方式,相对于饿汉式来说,不需要在类的初始化阶段就创建对象实例,只有在需要(即调用getInstance()方法)的时候才会进行对象实例创建,线程安全也由JVM保证。

​ JDK源码举例:
在这里插入图片描述

​ 上图是java.lang.Short源码中的内部类,将常用的整数保存到缓存池当中;下图是访问缓存池中的整数。类似的还有java.lang.Integer,java.lang.Long等包装类。

在这里插入图片描述

2.5 枚举

// enum实际上是extends抽象类java.lang.Enum
enum Singleton {
    instance
}

字节码反编译看下:
在这里插入图片描述

enum关键字修饰的类实际上继承了java.lang.Enum<E extends Enum
在这里插入图片描述
枚举类中声明的实例实际上是public static final修饰的常量
在这里插入图片描述
上图为枚举类中方法的字节码指令,也就是类的初始化阶段需要执行的逻辑(即将静态变量,静态代码块整合到一块)。

​ 红框1:创建Singleton枚举类对象实例,实际上调用了java.lang.Enum类的构造器(即方法),构造器参数是(“INSTANCE”, 0),可以通过ldc #7和iconst_0看出来;对象实例创建完成后,将实例引用赋给INSTANCE常量。

​ 红框2:将上一步创建的对象实例引用保存到枚举类内部数组$VALUES中,外部可以通过values()方法返回所有的枚举对象引用;数组的创建是在iconst_1和anewarray,意思是创建一个长度为1的引用类型数组。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/3336.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【SpringBoot】SpringBoot整合SpringSecurity+thymeleaf实现认证授权(配置对象版)

一.概述 1.框架概述 Spring Security 是 Spring 家族中的一个安全管理框架&#xff0c;Spring Security 的两大核心功能就是认证&#xff08;authentication&#xff09;和授权&#xff08;authorization&#xff09;。 认证 &#xff1a;你是什么人。授权 &#xff1a;你能…

RHCE学习 --- 第一次作业

RHCE学习 — 第一次作业 首先我们先设置网卡开机自动启动 vi /etc/sysconfig/network-scripts/ifcfg-ens160 最下面修改ONBOOTyes 然后安装好chrony&#xff0c;配置/etc/chrony.conf文件&#xff0c;添加题目要求的时间服务器 然后设置一个例行性工作&#xff0c;每天早上九…

美国FBA海运专线究竟是什么?美国fba海运专线都有那些?

美国FBA海运专线究竟是什么?美国FBA特别航运线&#xff0c;顾名思义就是海运发送的特别货运线&#xff0c;那么它的收费标准是什么呢?一、美国FBA海运专线究竟是什么 美国FBA特别航运线&#xff0c;顾名思义就是海运发送的特别货运线&#xff0c;那么它的收费标准是什么呢? …

猿创征文|『编程与创作』10款颜值颇高的宝藏工具

&#x1f31f;个人主页&#xff1a;Mymel_晗&#xff0c;一名喜欢鼓捣 Java 的在校学生。 &#x1f31f;撸代码本来是一件枯燥的事情&#xff0c;而一款高颜值工具加持可能会让你事半功倍&#xff0c;今天就给大家推荐一下我在大学学习中发现的几款颜值工具~ 从写代码&#xff…

录音m4a怎么转换成mp3

有小伙伴问手机录音文件电脑上播放不了怎么办&#xff1f;这是很多小伙伴在用手机录完音后遇到最多的问题&#xff0c;尤其是那些第一次遇到这个问题的人&#xff0c;根本不知道这是什么原因导致的&#xff0c;还总以为自己的录音文件出了问题&#xff0c;回去检查发现手机上还…

03-Nginx性能调优与零拷贝

目录 Nginx 性能调优 零拷贝&#xff08;Zero Copy&#xff09; 零拷贝基础 A、 实现细节 B、 总结 零拷贝方式 A、 实现细节 B、 总结 A、 实现细节 B、 总结 A、 实现细节 B、 总结 Nginx 性能调优 在 Nginx 性能调优中&#xff0c;有两个非常重要的理论点&#xff08;面试点…

【C++红黑树】带图详细解答红黑树的插入,测试自己的红黑树是否正确的代码

目录 1.红黑树的概念 1.1红黑树的特性&#xff08;41&#xff09; 2.红黑树的框架 3.红黑树的插入 3.1parent在grandfather的左边 3.1parent在grandfather的右边 4.测试自己的红黑树是不是平衡的 1.红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个…

米联客FDMA3.1数据缓存方案全网最细讲解,自创升级版,送3套视频和音频缓存工程源码

米联客的FDMA数据缓存方案发布也有五六年了&#xff0c;但真正能熟练使用的兄弟却很少&#xff0c;其实还是没有好的例程作为参考和同熟易懂的讲解&#xff0c;这里我做如下解析&#xff1a; FDMA部分&#xff1a;这部分是米联客封装了用户接口的AXI4-FULL协议代码&#xff0c;…

xhs-web校验流程分析

经测试&#xff0c;cookie中需携带gid和timestamp2。参数整理有点乱&#xff0c;仅供参考。 xhsFingerprintV3&#xff0c;VERSION: ‘2.1.2’ 文章目录流程概述timestamp2滑块验证参数Params轨迹FNcaptcha deviceIdProfileDatax-s-commonx5生成x8生成x9生成smidV2a1x-b3-trac…

Jenkins配置linux节点

之前在Windows下安装Jenkins 但是通过windows节点进行构建有诸多的不便&#xff0c;于是想到通过Jenkins里添加linux节点&#xff0c;让构建的时候&#xff0c;使用远程的linux服务器构建 目录一、配置凭据二、配置节点一、配置凭据 Manage Jenkins → Manage Credentials→Je…

Cadence Allegro PCB设计88问解析(十三) 之 Allegro中artwork层的建立

一个学习信号完整性的layout工程师 作为layout工程师&#xff0c;我们经常接触到的是PCB文件&#xff0c;用Cadence设计的是.brd文件。但是我们发给板厂的都是gerber文件。这就涉及到在我们设计好PCB文件之后&#xff0c;怎么把这些文件给到板厂。也就是我们Allegro中的artwork…

Java知识点--IO流(上)

Java知识点--IO流&#xff08;上&#xff09;一、文件1、文件的含义2、文件流二、常用的文件操作1、创建文件对象相关构造器和方法2、创建文件案例演示&#xff08;三种创建方法&#xff09;3、获取文件相关信息的方法4、获取文件相关信息方法案例演示5、目录的操作与删除6、应…

电脑系统重装下载的系统在哪找到

​因为现在很多人都在使用小白一键重装系统&#xff0c;但是很多人都不太能够了解小白系统重装下载的系统在哪儿&#xff0c;下面是小编提供的具体位置供大家参考。 工具/原料&#xff1a; 系统版本&#xff1a;win10 品牌型号&#xff1a;联想yoga13 软件版本&#xff1a;小白…

期货开户有什么规定

有很多朋友刚刚接触到资本市场&#xff0c;当他们听到期货时&#xff0c;他们觉得它非常遥远和高端&#xff0c;这是普通人无法接触到的。但事实上&#xff0c;情况并非如此。期货是一种非常普通的金融产品&#xff0c;很容易接触。让我们来看看期货开户有哪些必要条件以及有什…

postman环境变量的设置

背景&#xff1a;由于我们项目接口入参都有加密&#xff0c;每次接口调试都得启动项目&#xff0c;运行项目才能把对应的参数给传到后台&#xff0c;然后后台再解密参数&#xff0c;才能进行接口调试&#xff0c;很麻烦&#xff08;启动前端项目&#xff09;&#xff0c;如果接…

Linux篇【3】:Linux环境基础开发工具使用(中)

目录 一、Linux 编译器&#xff1a;gcc/g 的使用 1.1、知识拓展&#xff1a; 1.2、如何安装 C/C 标准静态库&#xff1a; 1.3、头文件与库文件&#xff1a; 1.4、静态库&#xff0c;静态链接&#xff0c;动态库&#xff0c;动态链接&#xff1a; 二、简单 vim 配置 2.1、…

【SpringBoot】一文了解SpringBoot热部署

文章目录前言手动启动热部署热部署种类手动进行热部署自动启动热部署热部署范围配置热部署的关闭总结&#x1f315;博客x主页&#xff1a;己不由心王道长&#x1f315;! &#x1f30e;文章说明&#xff1a;一文彻底搞懂SpringBoot热部署&#x1f30e; ✅系列专栏&#xff1a;Sp…

深入理解Java虚拟机:Java类的加载机制

本篇内容包括&#xff1a;Java 类的加载机制&#xff08;Jvm 结构组成、Java 类的加载&#xff09;、类的生命周期&#xff08;加载-验证-准备-解析-初始化-使用-卸载&#xff09;、类加载器 以及 双亲委派模型。 一、Java 类的加载机制 1、 Jvm 结构组成 Jvm 整体组成可分为…

坤坤音效键盘(Python实现)

文章目录坤坤音效键盘说明坤坤音效键盘效果展示代码实现安装第三方库准备音频监听键盘播放音频编写逻辑引入线程打包成exe程序坤坤音效键盘说明 坤坤音效键盘说明&#xff1a; 单独按下 j、n、t、mj、n、t、mj、n、t、m 按键&#xff0c;会对应触发 “鸡”、“你”、“太”、…

科技视界杂志科技视界杂志社科技视界编辑部2022年第21期目录

科技视界杂志科技视界杂志社科技视界编辑部2022年第21期目录 科普论坛《科技视界》投稿&#xff1a;cnqikantg126.com 天敌昆虫——让农业生产更安全 季香云; 1-3 储粮昆虫三维模型Web可视化技术研究与应用 阎磊;马宏琳;李亮;李鹏翔;王义超; 4-6 科学实验 非均匀催…