单例模式的创建(饿汉模式懒汉模式)

news2024/10/3 0:34:45

目录

一.什么是单例模式 

 二.用static来创建单例模式

 三.饿汉模式与懒汉模式

四.饿汉模式与懒汉模式的线程安全问题

五.New引发的指令重排序问题 

六.小结


一.什么是单例模式 

单例模式就是指某个类有且只有一个实例(instance)

这个是由需求决定的,有些需求场景就要求实例不能有多个,通过单例模式,相当于对'单个实例'做了一个更加严格的约束

单例模式本质上就是通过借助编程语言的特性,强行限制某个类,不能创建多个实例

 二.用static来创建单例模式

static修饰的成员/属性就变成了类成员/类属性,当属性变成类属性的时候,就是'单个实例'了,例如:

public class Demo{
    static String str = "";
}

更具体的说是类对象的属性,而类对象是通过JVM加载.class文件来的,此时类对象,其实在JVM当中也是'单例'

即JVM针对某个.class文件只会加载一次,也就只有一个类对象,类对象上面的成员(即static修饰的)也就只有一份了,在整个进程中都是独一无二的

class Singleton{
    private static Singleton instance = new Singleton();

    public static Singleton getInstance(){//该方法能让类外面获取到唯一的'实例'
        return instance;
    }

    //把构造方法设置为私有的,此时在类的外面就无法继续 new 一个新的实例了
    private Singleton(){

    };
}

在该单例类外面获取到单例的正确做法

Singleton instance = Singleton.getInstance();

 三.饿汉模式与懒汉模式

上面所写的代码实现单例模式的方式就叫做"饿汉模式" ,因为程序运行时用到了这个类就进行加载,这个实例在类加载阶段就创建好了,创建的时机非常早

还有另一种创建单例模式的方式叫做"懒汉模式",相比于"饿汉模式"创建实例的时机更迟,带来了更高的效率

class SingletonLazy{
    private static SingletonLazy instance = null;

    public static SingletonLazy getInstance(){
        //首次调用getInstance才会触发,后续调用getInstance发现instance不为空了,就不会再创建实例,立即返回instance
        if(instance == null){
            instance = new SingletonLazy();
        }
        return instance;
    }

    private SingletonLazy(){

    }
}

public class Demo2 {
    public static void main(String[] args) {
        SingletonLazy instance = SingletonLazy.getInstance();
    }
}

如果后面没人调用getInstance,这样就把构造实例的过程给节省下来了,效率也就提升了

或者即使有代码后面调用getInstance,但是调用的时机就比较晚,创建实例的时机也就迟了,能够和其他的耗时操作岔开了(一般程序刚启动跑起来的时候,要初始化的东西比较多,系统资源比较紧张),而饿汉模式刚开始就创建实例,与其他所需要初始化的东西竞争,使系统资源雪上加霜

所以使用懒汉模式比使用饿汉模式创建单例模式更好 

四.饿汉模式与懒汉模式的线程安全问题

虽然懒汉模式创建单例模式更好,但还要考虑线程安全问题,即多个线程并发运行的时候

饿汉模式属于线程安全,而懒汉模式属于线程不安全

在饿汉模式的 getInstance 方法里面并没有关于修改的操作,只涉及到读操作,所以饿汉模式是线程安全的

而懒汉模式的 getInstance 方法里面既涉及到读操作也涉及到修改操作,线程就不安全了

在有多个线程的时候,例如线程1 LOAD 完进行 CMP 发现 instance 为空就NEW一个新的实例,但线程二在线程1 LOAD 完后也 LOAD 然后线程1 CMP 完线程2也 CMP ,此时线程1还没进行修改操作,所以线程2也判断 instance 为null,然后也就NEW了一个实例,这样就会导致实例被创建出来了多份

如何才能让懒汉模式线程安全?

就是让以上的操作变成原子的,即加锁

public static SingletonLazy getInstance(){
        synchronized (SingletonLazy.class){//在读之前加上一把锁
            if(instance == null){
                instance = new SingletonLazy();
            }
        }//修改完毕后解锁
        return instance;
    }

该线程不安全的时候只是在实例创建之前(首轮调用的时候)才会触发线程不安全的问题,在实例创建好之后if条件就进不去,就只变成读操作,但是仍然会频繁的加锁和解锁,就会产生很大的开销,可能涉及到用户态和内核态之间的切换,代价比较大

所以需要判断什么时候需要加锁,什么时候不需要加锁,

实例创建之前,线程是不安全的,就需要加锁,实例创建之后,线程是安全的,就不需要加锁

因此可以在外面加上一层判断条件

    public static SingletonLazy getInstance(){
        if(instance == null){//判断是否要加锁
            synchronized (SingletonLazy.class){
                if(instance == null){//判断是否要创建实例
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }

因为两个判断之间隔着一个加锁操作,加锁可能导致锁竞争,锁竞争就会导致阻塞,不知道什么时候能够唤醒,所以第一个if和第二个if的结果可能是截然不同的,即第一个if成立了,第二个if不一定成立

五.New引发的指令重排序问题 

new操作本质上分为3个步骤:

①申请内存,获取内存首地址

②调用构造方法,来初始化实例

③ 把内存的首地址赋值给instance引用

单线程的情况下,步骤②和步骤③的执行的先后顺序没什么差别,就可能发生指令重排序,执行的顺序为①③②

多线程的情况下,第一个线程的执行顺序为①③②,在步骤③执行结束之后,步骤②执行开始之前,第二个线程调用了getInstance方法,这个方法就会认为instance的值不为null,那么就直接返回 instance 实例,并对instance进行解引用操作(使用里面的属性/方法),但此时线程1的步骤②其实还没执行,instance的值还没有初始化仍然为空,线程2调用了就会出现问题

例如:老师布置一份作业说明天早上之前写完,你没写完,第二天早上以为不会上交,跟老师讲我写完了,老师突然说拿给他看看,你拿不出来,这不就坏事了,这个情况跟指令重排序带来的问题是一样的.

那么如何禁止发生指令重排序呢?就是使用volatile来禁止

private volatile static SingletonLazy instance = null;//volatile用来禁止指令重排序

就是直接给instance加上volatile禁止有关instance的操作发生指令重排序

六.小结

创建线程安全的单例模式有三个要注意的点:

1.加锁将读写操作变为原子的

2.双重if避免不必要的加锁解锁

3.加volatile防止有关实例的操作指令重排序

 

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

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

相关文章

关于mysql学习

1.索引 1.1 索引概述 Mysql官方对索引的定义是:索引(index)是帮助mysql高效获取数据的数据结构(有序)。在数据库之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某些方式引用(指向)数据,这样就可以在数据结构…

密码技术扫盲:对称加密

个人博客 🎯 密码技术扫盲:对称加密密码技术扫盲:非对称加密密码技术扫盲:认证 人类最较真、技艺最精湛的事业是军事,密码技术最大放异彩的地方也在军事,战争中需要通过无线电或其他手段来传达指令&#…

LeetCode刷题复盘笔记—一文搞懂动态规划之309. 最佳买卖股票时机含冷冻期问题(动态规划系列第二十四篇)

今日主要总结一下动态规划的一道题目,309. 最佳买卖股票时机含冷冻期 题目:309. 最佳买卖股票时机含冷冻期 Leetcode题目地址 题目描述: 给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。​ 设计一个算法计算…

Python编程 函数的定义与参数

作者简介:一名在校计算机学生、每天分享Python的学习经验、和学习笔记。 座右铭:低头赶路,敬事如仪 个人主页:网络豆的主页​​​​​​ 目录 前言 一.函数 1.函数例子 不会让代码重复的出现。CVout 2.函数介绍(熟悉) 3.…

如何在AdsPower中设置Oxylabs住宅代理和数据中心代理?

AdsPower是一款适用于Windows和Mac系统的浏览器管理工具,允许多用户登录。AdsPower的主要功能有多账户管理、浏览器指纹处理等。 集成操作流程 在官网(www.adspower.com/download)下载AdsPower并完成安装工作后,单击新建配置文件…

分布式文件系统之NFS

「分布式」是现在蛮流行的一个词,而其盛行,离不开底层网络通信能力的迅速发展。在文件系统这个领域,早期的分布式实现更多的是用来实现「共享」,而不是「容错」。传统的集中式文件系统允许单个系统中的多用户共享本地存储的文件&a…

SVG公众号排版 | GIF动图如何禁止循环播放?PS设置了也没用!

在SVG公众号排版中,我们经常使用到GIF动图,有些排版需求是想让GIF动图一直无限循环播放,也有其他排版需求是只想让GIF动图播放一次就停止了,这种情况我们可以通过Photoshop软件来设置GIF动图的播放次数,详见下图。 但是呢,也有一种情况,即使在Photoshop软件设置了GIF动图…

大话设计模型 Task02:策略、装饰、代理

目录一、简单工厂模式问题描述模式定义问题分析代码实现二、策略模式问题描述问题分析模式定义代码实现三、装饰模式问题描述问题分析模式定义代码实现四、代理模式问题描述问题分析模式定义代码实现五、工厂方法模式问题描述问题分析模式定义简单工厂 vs. 工厂方法代码实现一、…

上传项目代码到Github|Gitee

上传项目代码到Github|Gitee 文章目录上传项目代码到Github|Gitee1、前置准备1.1 Git 安装1.2 在 Git 中设置用户名1.2.1 为计算机上的每个存储库设置 Git 用户名1.2.2 为一个仓库设置 Git 用户名1.3 SSH免密登录1.4 Github创建一个新的仓库2、上传项目2.1 初始化本地库2.2 添加…

蓝桥杯入门即劝退(十六)查找元素范围(双解法)

欢迎关注点赞评论,共同学习,共同进步! ------持续更新蓝桥杯入门系列算法实例-------- 如果你也喜欢Java和算法,欢迎订阅专栏共同学习交流! 你的点赞、关注、评论、是我创作的动力! -------希望我的文章…

什么是制造业数字化转型?制造业数字化转型的核心与意义

对于生产制造企业来讲,当下如果不进行数字化转型的话,很大概率会被时代所抛弃的。为什么这么讲?因为在未来的很长一段时间,你可以充分了解到,数字化转型已然成为了制造业向前的主旋律。既然数字化势在必行,…

可以赚钱的副业项目,简单易上手兼职副业推荐

在当前的经济环境下,对每个人来说,仅仅依靠那点薪水生活是非常紧张的。为了改善你的生活,你需要找到其他赚钱的方法,在互联网上做兼职是一个不错的选择。 今天推荐几个普通人可以做的兼职副业,希望对大家有所帮助。 一…

微信公众号的文章可以修改几次?修改的步骤有哪些

许多小伙伴们在运营微信公众号的时候,可能会遇到过这些难题,在发布微信公众号之前检查没有检查好,导致有错字或者是错句。有的时候可能配图还会配错! 今天伯乐网络传媒就给大家带来一些实用的东西,比如微信公众号可以…

深入理解 Python 的对象拷贝和内存布局

深入理解 Python 的对象拷贝和内存布局 前言 在本篇文章当中主要给大家介绍 python 当中的拷贝问题,话不多说我们直接看代码,你知道下面一些程序片段的输出结果吗? a [1, 2, 3, 4] b a print(f"{a } \t|\t {b }") a[0] 100…

微信小程序的自定义组件(1)

文章目录1. 自定义组件2. 组件样式3. 组件-数据、方法和属性4. 组件数据监听器5. 组件纯数据字段1. 自定义组件 Component(Object object) | 微信开放文档 (qq.com) 创建组件 在项目的根目录中,鼠标右键,创建components->test文件夹在新建的componen…

第十二章 计算学习理论

12.1 基础知识 计算学习理论研究的关于通过计算来进行学习的理论。即关于机器学习的理论基础,其目的是分析学习任务的困难本质,为学习算法提供理论保证,并根据分析结果指导算法设计。 12.2 PAC学习 计算学习理论中最基本的是概率近似正确&…

点击化学染料DBCO-PEG-CY7.5|Cyanine7.5-PEG-DBCO|花青素Cyanine7.5

​DBCO-PEG-CY7.5点击化学染料其中Cy7.5 (Cyanine 7.5) 是一种发近红外(NIR)荧光的花青素荧光染料。根据磺化与否,分为普通Cy7.5和磺化Cy7.5,但常常统称为Cy7.5。 Cy7.5的消光系数高,荧光也很亮,并且对pH不…

为什么很多人转行学习Web前端技术?

为什么很多人转行学习Web前端技术?不管你是工人阶层还是服务行业,是否想过转行IT,转行IT后肯定会选择一门编程语言进行深入学习,很多转行的人基础都不是太好,不是科班出身,甚至有的是专科乃至中专,前端的H…

cuda学习笔记3——cuda常用内存相关函数及其使用示例

cuda学习笔记3——cuda常用内存相关函数及其使用示例常用的GPU内存函数cudaMalloc()cudaMemcpy()cudaFree()代码示例常用的GPU内存函数 cuda程序将系统区分成host和device,二者有各自的memory。kernel可以操作device memory,为了能很好的控制device端内…

软件测试人员去外包公司待遇怎么样?外包薪资高吗?

📌 博客主页: 程序员二黑 📌 专注于软件测试领域相关技术实践和思考,持续分享自动化软件测试开发干货知识! 📌 公号同名,欢迎加入我的测试交流群,我们一起交流学习! 可能…