单例模式以及常见的两种实现模式

单例模式是校招中最常考的设计模式之一.

设计模式其实就是类似于“规章制度”,按照这个套路来进行操作。

单例模式能保证某个类在程序中只存在唯一 一份实例。而不会创建出多个实例,如果创建出了多个实例,就会编译报错。而不会创建出多个实例,如果创建出了多个实例,就会编译报错。不使用单例模式也可以做到,就像跟别人借钱说我一定会还一样,但是模式就相当于打了欠条,一定得做到的。这一点在很多场景上都需要. 比如 JDBC 中的 DataSource 实例就只需要一个。

单例模式具体的实现方式, 分成 "饿汉" 和 "懒汉" 两种.

饿汉模式

类加载的同时, 创建实例(给人一种很急的感觉)

// 饿汉模式的 单例模式 实现.
// 此处保证 Singleton 这个类只能创建出一个实例.
class Singleton {
    // 在此处, 先把这个实例给创建出来了.
    private static Singleton instance = new Singleton();

    // 如果需要使用这个唯一实例, 统一通过 Singleton.getInstance() 方式来获取.
    public static Singleton getInstance() {
        return instance;
    }

    // 为了避免 Singleton 类不小心被复制出多份来.
    // 把构造方法设为 private. 在类外面, 就无法通过 new 的方式来创建这个 Singleton 实例了!!
    private Singleton() {}
}

public class ThreadDemo19 {
    public static void main(String[] args) {
        Singleton s = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();

        // Singleton s3 = new Singleton();
        System.out.println(s == s2);
    }
}

        运行一个 Java 程序,会先让 Java 进程找到并读取对应的 .class 文件,就会读取文件内容并解析,构造成类对象......这一系列的过程操作就叫做 类加载。 

        因为 static 修饰的变量落入到了类对象里面,又因为类对象是在类加载阶段内创建出来的唯一一个实例,同时构造方法是 private 修饰的,因此就只有这一个实例的成员了。

懒汉模式

类加载的时候不创建实例,第一次使用的时候才创建实例,如果不用就不创建了(效率更高了)

class SingletonLazy {
    private static SingletonLazy instance = null;

    public static SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }

    private SingletonLazy() {}
}

public class ThreadDemo20 {
    public static void main(String[] args) {
        SingletonLazy s = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
        System.out.println(s == s2);
    }
}

         上述的这两种模式,饿汉模式只涉及到“读操作”,懒汉模式既涉及到“读操作”也涉及到“写操作”,因此这个在多线程环境下会有线程安全问题。

因此加上 synchronized 可以改善这里的线程安全问题。

public static SingletonLazy getInstance() {
        // 这一层 if 是因为只要对象被 new 了一次就不用再加锁产生更多开销了
        if (instance == null) {
            synchronized (SingletonLazy.class) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }

         也就是说,如果对象还有没有创建那么就要进行加锁,如果对象已经创建过了就不用加锁了,因为最后都是“读操作”,此时不加锁也没事。

懒汉模式-多线程版(改进)

        假设有很多线程都去进行 getInstance,这个时候就会出现内存可见性问题(编译器优化:只有第一次真正读了内存,后续都是读寄存器 / cache)

        同时还会有指令重排序问题:

instance = new Singleton();可以拆分成三个步骤

1.申请内存空间

2.调用构造方法,把这个内存空间初始化成一个合理的对象

3.把内存空间的地址赋值给 instance 引用

        正常情况下是1 2 3 顺序来执行的,但是编译器会为了提高效率从而调整顺序,可能就变成1 3 2,如果是单线程就没有区别。但在多线程环境下,假设 t1 是按照 1 3 2 执行的,当 t1 执行到 1 3 之后,准备执行 2 的时候,t2 跑过来执行了。此时在 t2 的角度 instance 就非空了,就会直接返回instance 了,但由于 t1 的 2 指令还没执行完,t2 拿到的是一个非法的对象(还没构造完成的不完整的对象),这时候如果尝试使用引用中的属性就会出现错误。例如 instance 里有个成员 num,构造方法是要初始化成100的,但是由于上述问题就导致构造方法还没执行,此时访问 num 是 0。

        因此加上 volatile 可以解决内存可见性问题和禁止指令重排序。

class SingletonLazy {
    private volatile static SingletonLazy instance = null;

    public static SingletonLazy getInstance() {
        if (instance == null) {
            synchronized (SingletonLazy.class) {  //加锁不是这个线程就一直赖着不走,而是切换调度正常,但是其他线程尝试加锁的时候就会阻塞。
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }

    private SingletonLazy() {}
}

public class ThreadDemo20 {
    public static void main(String[] args) {
        SingletonLazy s = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
        System.out.println(s == s2);
    }
}

理解双重 if 判定 / volatile:

加锁 / 解锁是一件开销比较高的事情. 而懒汉模式的线程不安全只是发生在首次创建实例的时候.

因此后续使用的时候, 不必再进行加锁了.

外层的 if 就是判定下看当前是否已经把 instance 实例创建出来了.

同时为了避免 "内存可见性" 导致读取的 instance 出现偏差, 于是补充上 volatile .

当多线程首次调用 getInstance, 大家可能都发现 instance 为 null, 于是又继续往下执行来竞争锁,

其中竞争成功的线程, 再完成创建实例的操作.

当这个实例创建完了之后, 其他竞争到锁的线程就被里层 if 挡住了. 也就不会继续创建其他实例.

1) 有三个线程, 开始执行 getInstance , 通过外层的 if (instance == null) 知道了实例还没

有创建的消息. 于是开始竞争同一把锁.

2) 其中线程1 率先获取到锁, 此时线程1 通过里层的 if (instance == null) 进一步确认实例是

否已经创建. 如果没创建, 就把这个实例创建出来.

3) 当线程1 释放锁之后, 线程2 和 线程3 也拿到锁, 也通过里层的 if (instance == null) 来

确认实例是否已经创建, 发现实例已经创建出来了, 就不再创建了.

4) 后续的线程, 不必加锁, 直接就通过外层 if (instance == null) 就知道实例已经创建了, 从

而不再尝试获取锁了. 降低了开销.

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

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

相关文章

MySQL优化慢SQL的6种方式

⛰️个人主页: 蒾酒 🔥系列专栏:《mysql经验总结》 🌊山高路远,行路漫漫,终有归途 目录 写在前面 优化思路 优化方法 1.避免查询不必要的列 2.分页优化 3.索引优化 4.JOIN优化 5.排序优化 6.UNION 优化…

今天掏心窝子!聊聊35岁了程序员何去何从?

今天的内容不聊技术,聊聊轻松的话题,脑子高速转了好几周,停下来思考一下人生…… 不对,关于35岁的问题好像也不轻松,些许有点沉重,反正不是技术,不用高速转动脑细胞了,哈哈。 兄弟…

牛客 NC36 在两个长度相等的排序数组中找到上中位数【中等 模拟 Java,Go,PHP】

题目 题目链接: https://www.nowcoder.com/practice/6fbe70f3a51d44fa9395cfc49694404f 思路 直接模拟2个数组有顺序放到一个数组中,然后返回中间的数参考答案java import java.util.Scanner;// 注意类名必须为 Main, 不要有任何 package xxx 信息 pu…

图文教程 | 2024Typora最新版免费激活使用教程(新旧版可用)

一、打开官网下载最新版Typora Typora 官网下载 安装: Typora中文官网:https://typoraio.cn/ Typora官网:https://typora.io/releases/all 官网长这个样子 下面这个不是官网!!!!注意&#x…

【ArcGIS 脚本工具】在ArcPro中实现mdb转gdb

ArcGIS Pro作为主力使用很久了,但是ArcMap也从来没有卸载过。 要问为什么,就是还需要ArcMap来读写mdb数据库,Pro是不支持读写mdb数据库的。 我之前尝试过不借助ArcMap把mdb转成gdb,奈何技术太菜搞不定。 直到我看到了公众号【G…

基于springboot实现洗衣店订单管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现洗衣店订单管理系统演示 摘要 随着信息互联网信息的飞速发展,无纸化作业变成了一种趋势,针对这个问题开发一个专门适应洗衣店业务新的交流形式的网站。本文介绍了洗衣店订单管理系统的开发全过程。通过分析企业对于洗衣店订单管理系统…

JD抓包 | 安卓app抓包

去年11月份左右搞过一次安卓抓包, 搞了很久试了很多方法, 才弄好. 时隔半年, 安卓抓包依然是令我头疼的问题 这次简单记录一下过程(细节太多我也说不清) JD的有效信息接口通常是以下这样的, 其他的接口并没有返回太多"有用"的信息 https://api.m.jd.com/client.act…

快速入门深度学习9.1(用时20min)——GRU

速通《动手学深度学习》9.1 写在最前面九、现代循环神经网络9.1 门控循环单元(GRU)9.1.1. 门控隐状态9.1.1.1. 重置门和更新门9.1.1.2. 候选隐状态9.1.1.3. 隐状态 9.1.3 API简洁实现小结 🌈你好呀!我是 是Yu欸 🌌 20…

从零全面认识 多线程

目录 1.基本概念 2.创建线程方式 2.1直接建立线程 2.2实现Runnable接口 3.3实现Callable接口 3.4 了解Future接口 Future模式主要角色及其作用 3.5实例化FutureTask类 3.实现线程安全 3.1定义 3.2不安全原因 3.3解决方案 3.4volatile与synchronized区别 3.5Lock与…

【Java集合进阶】数据结构(二又树,二又查找树,平衡二又树)

🍬 博主介绍👨‍🎓 博主介绍:大家好,我是 hacker-routing ,很高兴认识大家~ ✨主攻领域:【渗透领域】【应急响应】 【Java】 【VulnHub靶场复现】【面试分析】 🎉点赞➕评论➕收藏 …

c++的友元函数,详细笔记,细说三种友元用法

解释友元 友元用通俗易懂的话来说,就是:当有人来到你家里,他就只能呆在客厅里面,你是不可能让他来到你的卧室之中的。但是如果这个人是你的朋友,那么你是默许他可以进入你的卧室的。 此时呢?我告诉你&…

WXML模板语法-条件与列表渲染

wx:if 在小程序中&#xff0c;使用wx:if"{{condition}}"来判断是否需要渲染该代码 也可以用wx:elif和wx:else来添加else判断 <!--pages/ifIndex/ifindex.wxml--> <view wx:if"{{type 1}}">男</view> <view wx:elif"{{type …

卷积神经网络结构组成与解释

卷积神经网络结构组成与解释 卷积神经网络是以卷积层为主的深度网路结构&#xff0c;网络结构包括有卷积层、激活层、BN层、池化层、FC层、损失层等。卷积操作是对图像和滤波矩阵做内积&#xff08;元素相乘再求和&#xff09;的操作。 1. 卷积层 常见的卷积操作如下&#x…

基于Java的应急资源管理系统 (源码+文档+包运行)

一.系统概述 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本应急资源管理系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数…

Geeker-Admin:基于Vue3.4、TypeScript、Vite5、Pinia和Element-Plus的开源后台管理框架

Geeker-Admin&#xff1a;基于Vue3.4、TypeScript、Vite5、Pinia和Element-Plus的开源后台管理框架 一、引言 随着技术的不断发展&#xff0c;前端开发领域也在不断演变。为了满足现代应用程序的需求&#xff0c;开发人员需要使用最新、最强大的工具和技术。Geeker-Admin正是…

Linux 文件页反向映射

0. 引言 操作系统中与匿名页相对的是文件页&#xff0c;文件页的反向映射对比匿名页的反向映射更为简单。如果还不清楚匿名页反向映射逻辑的&#xff0c;请移步 匿名页反向映射 1. 文件页反向映射数据结构 struct file&#xff1a; 用户进程每open()一次文件&#xff0c;则会生…

分布式事务(一)

一、序言 本文介绍分布式事务相关的基本概念。 二、什么是分布式事务 分布式事务是指涉及多个独立计算机或系统的事务操作&#xff0c;这些计算机或系统可能位于不同的物理位置&#xff0c;彼此之间通过网络进行通信。分布式事务的目标是确保在分布式环境中的多个参与者之间…

物联网:门锁RNBN-K18使用记录

摘要&#xff1a;对 RNBN品牌下 K18智能门锁日常使用操作经验记录。 常见问题&#xff1a; 1.门锁联网时&#xff0c;找不到 wifi 怎么办。 答&#xff1a;检查一下几个方面&#xff1a;1. wifi 信号是否是2.4G&#xff0c;2.wifi信号是否距离没锁很远。因为门锁只能获取到2…

数据分析案例(三):基于RFM分析的客户分群

实验2 基于RFM分析的客户分群 Tips&#xff1a;"分享是快乐的源泉&#x1f4a7;&#xff0c;在我的博客里&#xff0c;不仅有知识的海洋&#x1f30a;&#xff0c;还有满满的正能量加持&#x1f4aa;&#xff0c;快来和我一起分享这份快乐吧&#x1f60a;&#xff01; 喜欢…

Alibaba --- 如何写好 Prompt ?

如何写好 Prompt 提示工程&#xff08;Prompt Engineering&#xff09;是一项通过优化提示词&#xff08;Prompt&#xff09;和生成策略&#xff0c;从而获得更好的模型返回结果的工程技术。总体而言&#xff0c;其实现逻辑如下&#xff1a; &#xff08;注&#xff1a;示例图…