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.mfbz.cn/a/2005.html

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

相关文章

01 | Msyql系统架构

目录MySQL系统架构连接器查询缓存分析器优化器执行器MySQL系统架构 大体来说,MySQL分为Server层和引擎层两部分。 Server层包含链接器、查询缓存、分析器、优化器和执行器,而引擎层负责的是数据的存储和读取,支持InnoDB、Myisam、Memory等多…

CSS实现文字凹凸效果

使用两个div分别用来实现凹凸效果;text-shadow语法 text-shadow: h-shadow v-shadow blur color; h-shadow:必需。水平阴影的位置。允许负值。 v-shadow :必需。垂直阴影的位置。允许负值。 blur:可选,模糊的距离。 co…

【C语言】你真的了解结构体吗

引言✨我们知道C语言中存在着整形(int、short...),字符型(char),浮点型(float、double)等等内置类型,但是有时候,这些内置类型并不能解决我们的需求,因为我们无法用这些单一的内置类型来描述一些复杂的对象&#xff0c…

k8s部署prometheus

k8s部署prometheus 版本说明: k8s:1.24.4 prometheus:release-0.12(https://github.com/prometheus-operator/kube-prometheus.git) 本次部署采用operator的方式将prometheus部署到k8s中,需对k8s和prom…

springboot+vue驾校管理系统 idea科目一四预约考试,练车

加大了对从事道路运输经营活动驾驶员的培训管理力度,但在实际的管理过程中,仍然存在以下问题:(1)管理部门内部人员在实际管理过程中存在人情管理,不进行培训、考试直接进行发证。(2)从业驾驶员培训机构不能严格执行管理部门的大纲…

SpringBoot解析指定Yaml配置文件

再来个文章目录 文章目录前言1、自定义配置文件2、配置对象类3、YamlPropertiesSourceFactory下面还有投票,帮忙投个票👍 前言 最近在看某个开源项目代码并准备参与其中,代码过了一遍后发现多个自定义的配置文件用来装载业务配置代替数据库…

使用 Python 从点云生成 3D 网格

从点云生成 3D 网格的最快方法 已经用 Python 编写了几个实现来从点云中获取网格。它们中的大多数的问题在于它们意味着设置许多难以调整的参数,尤其是在不是 3D 数据处理专家的情况下。在这个简短的指南中,我想展示从点云生成网格的最快和最简单的过程。…

继承和派生

🐶博主主页:ᰔᩚ. 一怀明月ꦿ ❤️‍🔥专栏系列:线性代数,C初学者入门训练,题解C,C的使用文章,「初学」C 🔥座右铭:“不要等到什么都没有了,才下…

手撕数据结构—队列

队列队列的话只允许在一端插入,在另外一端删除。插入数据的那一段叫做队尾,出数据的那一段叫做队头(从尾巴插入)。因此的话队列是先进先出的。入的顺序与出的顺序的话是一样的。这个与栈是不一样的,因为栈的话就是说如…

问题【Java 基础】

基础1、成员变量与局部变量的区别2、静态变量有什么作用3、字符型常量和字符串常量的区别4、静态方法为什么不能调用非静态成员5、静态方法和实例方法有何不同6、重载和重写有什么区别7、什么是可变长参数8、Java 中的几种基本数据类型了解么9、基本类型和包装类型的区别10、包…

【数据结构】树和二叉树的概念及结构

目录 1.树概念及结构 1.1 树的概念 1.2 树的相关概念 1.3树的表示 1.4 树在实际中的应用 2.二叉树概念及结构 2.1 概念 2.2 特殊的二叉树 2.2.1 满二叉树 2.2.2 完全二叉树 1.树概念及结构 1.1 树的概念 树是一种非线性的数据结构,它是由n(n>0) 个有…

一款专门为自动化测试打造的集成开发工具【Aqua】,“能快速构建自动化测试项目”,就问你爽不爽吧,,,

你好,我是不二。 随着行业内卷越来越严重,自动化测试已成为测试工程师的必备技能,谈及自动化测试肯定少不了编程,说到编程肯定离不开集成开发工具,比如:IntelliJ IDEA可以帮助我们快速构建Maven项目、sprin…

前端已死?后端已亡?弯弯绕绕,几分真几分假

前段时间,我在掘金分享了一篇GPT-4 性能文章,也许是过于强大带来的威胁性,引来评论区的排队哀嚎(如下图),所以“前端已死,后端已亡”这个概念真的成立吗?本文着重探讨前端。 前端和后…

警惕,3月20日WOS目录更新,50本SCI/SSCI被剔除,这个出版社多达18本

2023年3月SCI、SSCI期刊目录更新 2023年3月20日,Web of Science核心期刊目录再次更新!此次2023年3月SCIE & SSCI期刊目录更新,与上次更新(2023年2月)相比,共有50本期刊被剔除出SCIE & SSCI期刊目录…

[ 网络 ] 应用层协议 —— HTTP协议

目录 1.HTTP协议 1.1URL urlencode和urldecode 2. HTTP协议格式 HTTP请求 HTTP响应 3.告知服务器意图的HTTP方法 GET:获取资源 POST:传输实体主体 GET和POST的区别 使用Cookie的状态管理 4.返回结果的HTTP状态码 状态码告知从服务器端返回的…

三月份跳槽了,历经字节测开岗4轮面试,不出意外,被刷了...

大多数情况下,测试员的个人技能成长速度,远远大于公司规模或业务的成长速度。所以,跳槽成为了这个行业里最常见的一个词汇。 前几天,我看到有朋友留言说,他在面试字节的测试开发工程师的时候,灵魂拷问三小…

【Shell】脚本

Shell脚本脚本格式第一个Shell脚本:hello.sh脚本常用执行方式1. bash或sh脚本的相对路径或绝对路径2. 输入脚本的绝对路径或相对路径3. 在脚本的路径前加上.或者source脚本格式 脚本以#!/bin/bash开头(指定解析器) #! 是一个约定的标记&…

让 new bing 使用 GPT-4 编写一个令人满意的程序全过程赏析

让 new bing 使用 GPT-4 编写一个令人满意的程序全过程赏析 标签:new bing、GPT-4 文章目录让 new bing 使用 GPT-4 编写一个令人满意的程序全过程赏析前言1 让 bing 编写一个画螺旋线的程序1.1 我的要求(1)1.2 bing 的回答全文(…

p81 红蓝对抗-AWD 监控不死马垃圾包资源库

数据来源 注意:一下写的东西是在p80 红蓝对抗-AWD 模式&准备&攻防&监控&批量这篇文章的基础上进行的 演示案例: 防守-流量监控-实时获取访问数据包流量 攻击-权限维持-不死脚本后门生成及查杀 其他-恶意操作-搅屎棍发包回首掏共权限…

WPF 认识WPF

什么是WPF?WPF是Windows Presentation Foundation(Windows展示基础)简称,顾名思义是专门编写表示层的技术。WPF绚丽界面如下:GUI发展及WPF历史?Windows系统平台上从事图形用户界面GUI(Graphic User Interface)已经经历了多次换代&#xff0c…