多线程应用——单例模式

单例模式

文章目录

  • 单例模式
    • 一.什么是单例模式
    • 二.如何实现
      • 1.口头实现
      • 2.利用语法特性
    • 三.实现方式(饿汉式+懒汉式)
      • 1.饿汉式
      • 2.懒汉式
      • 3.线程安全的单例模式
      • 4.双重检查锁
      • 5.禁止指令重排序

一.什么是单例模式

单例模式(Singleton Pattern)顾名思义,在程序中一个类只有一个对象实例。例如我们在JDBC编程中,我们创建了一个简单类DataSource,只要从DataSource中获取数据库连接即可,不用创建多个DataSource对象。

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。

二.如何实现

1.口头实现

2.利用语法特性

  • 本质上就是利用编程语言自身的特性,强行限制某个类不能创建多个实例
  • static修饰一个变量后,这个变量就从一个普通的成员变量属性变成了类对象的成员变量
  • 在JVM中一个类只要一个类对象,从而保证了static变量的唯一性

三.实现方式(饿汉式+懒汉式)

1.饿汉式

public class SingletonHungry{
    //类的成员变量
    private static Singleton instance=new Singleton();
    //私有化构造方法
    private SingletonHungry(){ }
    
    /**
     * 对外获取类成员方法
     * @return
     */
    public static SingletonHungry getInstance(){
        return instance;
    }
}

饿汉式:需要急迫的创建这个实例,类在加载的过程中就创建出来了

描述:这种方式比较常见,但容易产生垃圾对象

  • 优点:没有加锁,执行效率高
  • 缺点:类加载时就初始化,浪费内存

2.懒汉式

public class SingletonLazy{
    //类的成员变量
    private static Singleton instance=null;
    //私有化构造方法
    private Singleton(){ }
    
    /**
     * 对外获取类成员方法
     * @return
     */
    public static Singleton getInstance(){
        //判断一个需要返回的对象是否为空
        if (instance==null){
            //创建对象
            instance=new SingletonLazy();
        }
        //返回单例对象
        return instance;
    }
}

懒汉式:什么时候用什么时候才去创建,不要程序启动的时候创建,从而节省了程序启动时的开销

3.线程安全的单例模式

在多线程中,饿汉式只是获取变量而不是修改变量;而懒汉式是修改共享变量,因此存在线程安全问题。

我们用上面的代码做一测试

public class Demo_SingletonLazy {
    public static void main(String[] args) {
        //多个线程获取单例对象
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(() -> {
                SingletonLazy instance = SingletonLazy.getInstance();
                System.out.println(instance);
            });
            thread.start();
        }
    }
}

image-20230830183534647

我们知道造成线程安全问题的原因有 原子性、内存可见性、有序性

image-20230830185305419

通过上图分析得出问题:不满足原子性,那该如何解决呢,当然是加锁。

public class SingletonLazy{
    //类的成员变量
    private static Singleton instance=null;
    //私有化构造方法
    private Singleton(){ }
    
    /**
     * 对外获取类成员方法
     * @return
     */
    public static Singleton getInstance(){
        synchronized(SingletonLazy.class){
            //判断一个需要返回的对象是否为空
        	if (instance==null){
            	//创建对象
            	instance=new SingletonLazy();
        	}
        }
        //返回单例对象
        return instance;
    }
}

image-20230830185941493

加锁之后,我们看到问题也解决了,但此时还有一个非常严重的问题:效率问题

  1. 当变量没有初始化时,第一次创建可能会出现线程问题,因为多个线程可能创建实例
  2. 当实例变量被创建后,new操作将永远不会执行了,因为获取到的实例不为null了
  3. 那么synchronized的锁就没有必要加了,因为实例已经创建好了,之后线程拿到锁之后只是判断一下实例是否为空,不会去new了,如果不为null就什么也不干就把锁释放了,这样一来锁白加了,资源也白白浪费了

synchronizeed看上去是一个关键字,可能会涉及到用户态–>内核态之间的切换,这个成本是比较高的,我们为了保证程序正确执行的基础可以承担这个成本,但是没有必要做无用的消耗

4.双重检查锁

既然在第一次创建完实例后加锁是为了判断实例是否为空,那么不如将判断为空放到加锁之前,避免因为上述原因而造成资源浪费

public class SingletonDCL {

    //定义一个类的成员变量
    private static SingletonDCL instance=null;

    private SingletonDCL(){}

    public static SingletonDCL getInstance(){
        //第一层判断是否需要加锁
        if (instance==null){
            synchronized (SingletonDCL.class){
                //第二层加锁判断是否需要创建对象
                if (instance==null){
                    //创建对象
                    instance=new SingletonDCL();
                }
            }
        }
        //返回单例对象
        return instance;
    }
}

5.禁止指令重排序

上述代码还存在一个严重问题,那就是指令重排序问题

假设一个线程在调用getInstande()方法时,拿到了锁,进入了第二层开始new对象:

new对象本质分为三步:

  1. 申请内存空间
  2. 调用构造方法,初始化实例
  3. 把内存首地址赋给对象的引用

可以看出1和3有逻辑关系,2是在这个内存空间里填充数据

如果这里指令重排序,造成执行顺序为1 3 2 那么这个时候又有一个线程执行到第一层的判断,这里的instance就不为空了,返回一个没有完成初始化的对象。这种情况也是很危险的

为了防止指令重排序,给变量加入关键字volatile

public class SingletonDCL {

    //定义一个类的成员变量
    private static volatile SingletonDCL instance=null;//禁止指令重排序,也保证了在对共享变量修改时的内存可见性

    private SingletonDCL(){}

    public static SingletonDCL getInstance(){
        //第一层判断是否需要加锁
        if (instance==null){
            synchronized (SingletonDCL.class){
                //第二层加锁判断是否需要创建对象
                if (instance==null){
                    //创建对象
                    instance=new SingletonDCL();
                }
            }
        }
        //返回单例对象
        return instance;
    }
}

看完留个三连吧

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

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

相关文章

软件研发CI/CD流水线图解

当谈到现代软件开发流程时&#xff0c;持续集成&#xff08;Continuous Integration&#xff0c;简称CI&#xff09;和持续交付&#xff08;Continuous Delivery&#xff0c;简称CD&#xff09;是两个关键的实践。它们旨在加速开发流程、提高软件质量&#xff0c;并使软件发布更…

kafka架构和原理详解

Apache Kafka 是一个分布式流数据平台,用于高吞吐量、持久性、可扩展的发布和订阅消息。它具有高度的可靠性,被广泛用于构建实时数据流处理、日志收集和数据管道等应用。 基本架构 1. 主题(Topic): 主题是消息的逻辑分类生产者将消息发布到特定的主题中,而消费者可以订阅…

【spring】一文带你弄懂Spring Bean的生命周期(超详细,超级通俗易懂!!)

目录 首先让我们来回顾一下Spring中的Bean是什么 Bean的生命周期 然后让我们由浅及深的对Bean的创建过程进行了解 首先来看最核心的五步骤 然后让我们逐层递进&#xff0c;学习一下七步骤版本吧 现在我们就可以进一步了解十步骤的版本&#xff08;完整的生命周期&#xff…

LLMs之Code:Code Llama的简介、安装、使用方法之详细攻略

LLMs之Code&#xff1a;Code Llama的简介、安装、使用方法之详细攻略 导读&#xff1a;2023年08月25日(北京时间)&#xff0c;Meta发布了Code Llama&#xff0c;一个可以使用文本提示生成代码的大型语言模型(LLM)。Code Llama是最先进的公开可用的LLM代码任务&#xff0c;并有潜…

视频分割合并工具说明

使用说明书&#xff1a;视频分割合并工具 欢迎使用视频生成工具&#xff01;本工具旨在帮助您将视频文件按照指定的规则分割并合并&#xff0c;以生成您所需的视频。 本程序还自带提高分辨率1920:1080&#xff0c;以及增加10db声音的功能 软件下载地址 https://github.com/c…

C# 学习笔记--个人学习使用 <2>

C# 学习笔记 Chapter 2 比较硬的基础部分Section 1 委托Part 1 Action 与 func 委托的示例Part 2 自定义委托Part 3 委托的一般使用Part 4 委托的高级使用Part 5 适时地使用接口 Interface 取代一些对委托的使用 Section 2 事件Part 1 初步了解事件Part 2 事件的应用Part 3 事件…

一个简单的vim例子

一.欢迎来到我的酒馆 在本章节介绍vim工具。 目录 一.欢迎来到我的酒馆二.什么是vim 二.什么是vim 2.1什么是vim vim是一种Linux命令行类型 的文本编辑器。vim指的是"vi improved"&#xff0c;意思是vi工具的升级版。vim是基于vi实现的&#xff0c;它提供了…

【1654. 到家的最少跳跃次数】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 有一只跳蚤的家在数轴上的位置 x 处。请你帮助它从位置 0 出发&#xff0c;到达它的家。 跳蚤跳跃的规则如下&#xff1a; 它可以 往前 跳恰好 a 个位置&#xff08;即往右跳&#xff09;。它可以 …

C++中为什么有模板的函数不能和.h文件分离,即分别声明和定义

目录 1.查看问题 2.探索问题 3.解决问题 1.查看问题 1.先看下面三个文件 stack.h stack.cpp test.cpp 2.探索问题 有了解的小伙伴应该知道大概率是在预处理&#xff0c;编译&#xff0c;汇编&#xff0c;链接中编译环节出错了&#xff0c;它在其他文件中无法通过定义找到函…

MATLAB 2023安装方法之删除旧版本MATLAB,安装新版本MATLAB

说明&#xff1a;之前一直使用的是MATLAB R2020b&#xff0c;但最近复现Github上的程序时&#xff0c;运行不了&#xff0c;联系作者说他的程序只能在MATLAB 2021之后的版本运行&#xff0c;因此决定安装最新版本的MATLAB。 系统&#xff1a;Windows 11 需要卸载的旧MATLAB 版…

快手Java一面,全是基础

现在已经到了面试招聘比较火热的时候&#xff0c;准备面试的过程中&#xff0c;一定要多看面经&#xff0c;多自测&#xff01; 今天分享的是一位贵州大学的同学分享的快手一面面经。 快手一面主要会问一些基础问题&#xff0c;也就是比较简单且容易准备的常规八股&#xff0…

微信小程序云开发-云存储文件ID转http

一、前言 云开发的云储存文件默认是以cloudID的形式读取的&#xff0c;但是这种读取方式只能在微信小程序或内嵌H5中使用。 所以如果需要在其他地方使用&#xff0c;例如浏览器或网站等其他端读取文件的时候&#xff0c;需要转换成普通的http链接。 目前官方提供有转换的接口…

docker之Compose与DockerSwarm

目录 Compose 简介 概念 为什么需要&#xff1f; 配置字段 常用命令 安装 1.下载 2.授权 使用 1.创建文件 2.启动 docker Swarm 关键概念 调度策略 spread binpack random 特性 集群部署 1.准备 2.创建swarm并添加节点 在主服务器上创建swarm集群 节点…

8天长假快来了,Python分析【去哪儿旅游攻略】数据,制作可视化图表

目录 前言环境使用模块使用数据来源分析 代码实现导入模块请求数据解析保存 数据可视化导入模块、数据年份分布情况月份分布情况出行时间情况费用分布情况人员分布情况 前言 2023年的中秋节和国庆节即将来临&#xff0c;好消息是&#xff0c;它们将连休8天&#xff01;这个长假…

MongoDB入门

简介 MongoDB是一个开源、高性能、支持海量数据存储的文档型数据库 是NoSQL数据库产品中的一种&#xff0c;是最像关系型数据库&#xff08;MySQL&#xff09;的非关系型数据库 内部采用BSON(二进制JSON)格式来存储数据,并支持水平扩展。 MongoDB本身并不是完全免费的,它对于…

算法-图BFS/DFS-单词接龙

算法-图BFS/DFS-单词接龙 1 题目概述 1.1 题目出处 https://leetcode-cn.com/problems/number-of-islands 1.2 题目描述 给定两个单词&#xff08;beginWord 和 endWord&#xff09;和一个字典&#xff0c;找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如…

C++八股记录

C内存管理 C中&#xff0c;内存分成5个区。 栈&#xff1a;函数内局部变量&#xff1b;自动管理&#xff0c;效率高&#xff0c;但空间较小&#xff1b; 堆&#xff1a;new分配的内存块&#xff1b;手动管理&#xff0c;效率低&#xff0c;但空间大&#xff1b; 自由存储区&…

代码复现,我能行之DMP-MATLAB

代码复现&#xff0c;我能行——系列一 一、基础概念 Dynamic Movement Primitives &#xff08;DMP&#xff09;&#xff0c;中文为动态运动基元或动态运动原语&#xff0c;由美国University of Southern California的Stefan Schaal教授团队于2002年提出&#xff0c;是一种用…

2023年智慧政务一网通办云平台顶层设计与建设方案PPT

导读:原文《2023年智慧政务一网通办云平台顶层设计与建设方案PPT》(获取来源见文尾),本文精选其中精华及架构部分,逻辑清晰、内容完整,为快速形成售前方案提供参考。 部分内容:

计算机竞赛 基于Django与深度学习的股票预测系统

文章目录 0 前言1 课题背景2 实现效果3 Django框架4 数据整理5 模型准备和训练6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于Django与深度学习的股票预测系统 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff…