Java单例模式写法

目录

  • 单例模式
    • 饿汉模式实现单例
    • 懒汉模式实现单例
      • 单线程版
      • 多线程版
      • 多线程版优化
  • 小结

单例模式

单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例.
为什么要保证只存在一份对象呢?
因为有些对象管理的内存数据可能会很多, 可能有些项目里就一个对象运行起来就吃上百G的内存空间, 如果不小心多new了几个, 那系统可能直接崩溃了.

饿汉模式实现单例

类加载的同时, 创建实例.

class Singleton {
    private static Singleton singleton = new Singleton();
    private Singleton() {}    //不允许外部调用构造方法
    public static Singleton getSingleton() {
        return singleton;  //将创建好的实例返回
    }
}

这里只是单纯的读操作, 因此该模式是线程安全的.

懒汉模式实现单例

核心思想 :非必要不创建.
加载的时候不创建实例. 第一次使用的时候才创建实例.

单线程版

class Singleton {
    private static Singleton singleton = null;
    private Singleton() {}  //修改了构造方法的访问修饰权限符, 只有在类内部才能访问构造方法
    public static Singleton getSingleton() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

上面的懒汉模式实现是线程不安全的.
为什么不安全呢?
比如两个线程同时调用 getSingleton 方法时, 此时 singleton 还为空, t1 线程和 t2 线程都走进了判断语句, 判断通过, 它两都 new 出了对象, 这与我们预期的创建一个对象不符, 所以线程不安全.

我们可以通过加锁来解决这一问题, 下面是多线程版 , 线程安全.

多线程版

怎么加锁呢?
看看这样加锁是否可行:

class Singleton {
    private static Singleton singleton;
    private Singleton() {}
    public static Singleton getSingleton() {
        if(singleton == null) {
            synchronized (Singleton.class) {
                singleton = new Singleton();
            }
        }
        return singleton;
    }
}

显然这样是不行的, 当两个线程同时调用 getSingleton 方法时, 此时 singleton 还为空, t1 线程和 t2 线程都走进了判断语句, 判断通过, 然后通过竞争锁, 竞争成功的线程先 new 对象, 另一个线程后 new 对象, 这也是 new 了多个对象, 与预期不符.

我们再来看看给方法加锁:

class Singleton {
    private static Singleton singleton;
    private Singleton() {}
    public synchronized static Singleton getSingleton() {
        if(singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

当多个线程同时调用 getSingleton 方法时, 通过竞争锁, 竞争成功的线程先进去创建完对象出来后, 其他线程再来获取对象就不会再创建对象了.

其实这里还有一个问题, 那就是指令重排序问题, 什么意思呢?
举个例字 :
假设我们有两个线程 t1 和 t2,
t1 有个操作是: s = new Student();
t2 有个操作是: if(s != null) { s.learn(); }

这个操作 : s = new Student(); 大体可以分为三个步骤:

  1. 申请内存空间
  2. 调用构造方法(初始化内存的数据)
  3. 把对象的引用赋值给 s (内存地址的赋值)

如果是单线程, 此处进行指令重排序,步骤2和步骤3是可以调换顺序的, 重排序后可能就是132执行了, 这对单线程结果没影响, 但多线程就不行了.

回到上面的 t1 和 t2, 如果 t1 的操作进行指令重排序, 就会先申请内存, 然后把这个内存地址赋值给 s (注意这里还没有调用构造方法, 没有new对象), 这时 s 还是 null ,这个时候如果线程 t2 刚好进行 if 判断则会直接进入, 然后调用 s 的方法, 因为 s 为空, 就会抛出空指针异常.

这也是上面给方法加锁代码存在的问题, 如果得到的 singleton 为空就调用这里面的方法, 那就会产生空指针异常.

如何避免发生指令重排序呢?
我们可以加个 volatile 来禁止指令重排序, 在下面代码中体现.

多线程版优化

class Singleton {
    private volatile static Singleton singleton; //禁止对singleton进行指令重排序
    private Singleton() {}
    public static Singleton getSingleton() {
        //注意这里有两个 if 判断是否为空!!! 
        if(singleton == null) {
            synchronized (Singleton.class) {
                if(singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

为什么要两个 if 判断呢?
加锁 / 解锁是一件开销比较高的事情. 而懒汉模式的线程不安全只是发生在首次创建实例的时候.因此后续使用的时候, 不必再进行加锁了.
这样可以让加锁操作只在第一次创建实例的时候出现.

小结

单例模式线程安全问题 :

  1. 饿汉模式, 天然就是安全的, 只是读操作.

  2. 懒汉模式, 不安全的, 有读也有写.

如何将懒汉模式变安全:

  1. 加锁, 把 if new 变为原子操作.
  2. 双重 if, 减少不必要的加锁操作.
  3. 使用 volatile 禁止指令重排序, 保证后续线程拿到的是完整对象.

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

差分运放公式推导-运算放大器

不知道大家有没遇到这种情况,在计算电路的时候,有时候会突然的忘记一些公式啊啥的,需要回去翻看笔记或者查资料,知其然而不知其所以然。今天跟大家一起来一起推导一遍差分运放的计算过程。 计算过程其实归根结底还是根据运放的虚…

初阶C语言:冒泡排序

冒泡排序是一种简单的排序算法,它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。1.冒泡排序关于冒泡排序我们在讲…

我的 System Verilog 学习记录(10)

引言 本文简单介绍 SystemVerilog 的约束。 前文链接: 我的 System Verilog 学习记录(1) 我的 System Verilog 学习记录(2) 我的 System Verilog 学习记录(3) 我的 System Verilog 学习记…

CF1770E Koxia and Tree

CF1770E Koxia and Tree 题目大意 有一棵nnn个结点的无根树,边从111到n−1n-1n−1标号,第iii条边连接结点uiu_iui​和viv_ivi​。 有kkk个结点带有标记,分别是a1,a2,…ak(∀i≠j,ai≠aj)a_1,a_2,\dots a_k(\forall i\neq j,a_i\neq a_j)a1…

探索css渐变-实现饼图-加载图-灯柱

文章目录linear-gradient()线性渐变radial-gradient()圆形渐变conic-gradient() 锥形渐变锥形渐变实现加载动画渐变实现发廊灯柱css的渐变分为三种: 线性渐变:linear-gradient() 圆形渐变:radial-gradient() 锥形渐变:conic-gradi…

【Java】UDP网络编程

文章目录前言DatagramSocketDatagramPacket注意事项与区别代码演示前言 UDP(user datagram protocol)的中文叫用户数据报协议,属于传输层。 UDP是面向非连接的协议,它不与对方建立连接,而是直接把我要发的数据报发给对…

蓝桥杯算法全集之完全背包问题(动态规划算法)

一、概念定义有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。第 i种物品的体积是 vi,价值是 wi。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。用下面这个图来分别动态规…

蓝桥杯真题——自动售水机

2012年第四届全国电子专业人才设计与技能大赛“自动售水机”设计任务书1. 系统框图接下来我们将任务分块: 1. 按键控制单元 设定按键 S7 为出水控制按键,当 S7 按下后,售水机持续出水(继电器接通,指示 灯 L10 点亮&…

LeetCode:704. 二分查找

🍎道阻且长,行则将至。🍓 🌻算法,不如说它是一种思考方式🍀算法专栏: 👉🏻123 一、🌱704. 二分查找 题目描述:给定一个 n 个元素有序的&#xff…

区块链基本原理

区块链的起源 创始者介绍 姓名:中本聪(英语:SatoshiNakamoto),自称日裔美国人,日本媒体常译为中本哲史,此名是比特币协议及其相关软件Bitcoin-Qt的创造者,但真实身份未知。 中本聪于…

【jvm】JVM(三)JVM 垃圾回收算法详解(CMS、三色标记)

文章目录一、常见的垃圾回收算法分代收集理论标记复制算法标记清除算法标记整理算法二、垃圾收集器Serial收集器 (-XX:UseSerialGC)Serial Old 收集器(-XX:UseSerialOldGC)Parallel 收集器(-XX:UseParallelGC)— jdk 1.8新生代默认…

【进阶数据结构】二叉搜索树经典习题讲解

🌈感谢阅读East-sunrise学习分享——[进阶数据结构]二叉搜索树 博主水平有限,如有差错,欢迎斧正🙏感谢有你 码字不易,若有收获,期待你的点赞关注💙我们一起进步 🌈我们在之前已经学习…

Python读写EXCEL文件常用方法大全

python读写excel的方式有很多,不同的模块在读写的讲法上稍有区别,这里我主要介绍几个常用的方式。 用xlrd和xlwt进行excel读写;用openpyxl进行excel读写;用pandas进行excel读写; 一、数据准备 为了方便演示&#xff…

ChatGPT加强版GPT-4面世,打工人的方式将被颠覆

🔗 运行环境:chatGPT,GPT-4 🚩 撰写作者:左手の明天 🥇 精选专栏:《python》 🔥 推荐专栏:《算法研究》 #### 防伪水印——左手の明天 #### 💗 大家好&#…

遗传算法原理及案例解析

一、遗传算法原理遗传算法—进化算法(Genetic Algorithm GA)源自达尔文进化论的物竞天择适者生存思想。它是通过模拟生物进化的过程,搜索全局最优解的一种算法。算法可应用于优化问题,当一个问题有N种解决方案时,如何选…

SAP BPC简介

BPC是SAP在financial application领域主推的产品,由于从原有产品线发展而来,产品本身有两个版本,分别是基于MS OLAP平台和Netweaver OLAP平台。 整个系统分为.net前台和abap后台。由于abap端的数据结构与.net数据结构的差异,所以没…

vue大型商城系统中遇到的问题(上)

一:创建仓库1.领导创建git仓库(参考————这篇文章),新手下载git2.打开cmd终端,将git仓库拉到本地3.进入文件目录,查看分支(新手向——为什么需要创建分支,查看---)4.创…

【链表OJ题(九)】环形链表延伸问题以及相关OJ题

环形链表OJ题 1. 环形链表 链接:141. 环形链表 描述: 给你一个链表的头节点 head ,判断链表中是否有环。 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环&…

5. QtDesignStudio中的3D场景

1. 说明: 三维渲染开发是Design Studio的重要功能,且操作方便,设计效率非常高,主要用到的控件是 View3D ,可以在3D窗口中用鼠标对模型直接进行旋转/移动/缩放等操作,也可以为模型设置各种动画,执行一系列的…

关于element-plus按需引入时,在vite中使用自定义主题失效的问题解决

1. 问题产生过程描述: 1)使用vite创建vue3项目 2)按部就班的安装element-plus vue-router axios npm i element-plus vue-router axios -S 3) 把element-plus按需引入按照官网的步骤操作好 主题 | Element Plus 4)axios按…
最新文章