Java多线程案例之单例模式

news2024/11/17 12:22:04

目录

一、饿汉模式

二、懒汉模式

前言:单例模式是校招中最常见的设计模式之一。下面我们来谈谈其中的两个模式:懒汉,饿汉。

何为设计模式?

设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

日常开发中,虽然不能百分之白解决问题,但是大大提高了普通开发者的下限。

单例模式的概念

在应用这个模式时,单例对象的类必须保证只有一个实例存在。

为何有单例模式的出现?

许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。

比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。

这种方式简化了在复杂环境下的配置管理。

ps:说到这里我们想到Java中JDBC的DataSoucre只需要一个实例,但是如果我们想创建多个实例语法上是没有问题的(虽然会造成代码的冗余)。

因此单例模式本质上就是:借助编程语言自身的语法特性,强制限制某个类,不能创建多个实例。

一、饿汉模式

话不多说,直接看代码(注意看注解):

class Single{
    //用static修饰,这样就可以让instance变为这个类的唯一实例。
    private static Single instance = new Single();

    //因为要在不创建额外实例的情况下调用这个方法,必须将其设置为static方法
    public static Single getSingle() {
        return instance;
    }
    //为了防止Single在类外可以被实例,这边将其的构造方法设计未private。
    private Single() {

    }
}
public class Demo16 {
    public static void main(String[] args) {
        Single instance = Single.getSingle();
    }
}

需要注意的是:饿汉模式是在类加载阶段创建出实例的。(ps:类加载阶段创建实例相对于普通情况早了许多,这也是为什么叫“饿汉”的原因。一个饿了几天的人,对食物的没有抵抗力的,一下子就开始吃了。)

为何饿汉模式不需要考虑线程安全的问题?

首先,我们需要明白线程安全出现的原因是什么;该模式只涉及到单纯的读取数据,并不涉及修改,因此该模式线程安全。

二、懒汉模式

类加载的同时,不再创建实例,而是等第一次使用的时候再创建。(创建的时机更迟,很好的避开了程序刚启动时候资源紧张的情况,提高了效率。)

懒汉模式——单线程版本

class Singleton{
    private static Singleton instance = null;
    
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
    
    private Singleton() {
        
    }
    
}

懒汉模式——多线程版本

在多线程的环境下,如果不使用synchronized关键字进行加锁会引发线程安全问题,原因如下:

 实现代码:

class Singleton{
    private static Singleton instance = null;

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

    }
}

 仔细思考后,我们会发现其实上面多线程版本的懒汉模式是有一定问题的。

虽然通过加锁的方式解决了线程安全问题,但是在这同时又引入了新的问题,因为这里的线程不安全并不是永久性的,什么意思呢?

当我们代码创建了第一个实例之后,那么其实就不需要再执行if语句里面的内容了(条件判断失败),但是我们加锁了之后,就会导致每次执行这个方法,我们都需要进行加锁,这种无脑的加锁方式,会导致程序运行的开销变大(因为加锁可能设计 用户态->内核态 之间的转换,这样的转换成本很高)。

ps:这也是为什么Vector,StringBuffer ,HashTable等不推荐用的原因,因为它们的内部有许多像这样无脑加锁的代码。

解决方案:给外层的嵌套循环加上if。

实现代码:

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            } 
        }
        return instance;
    }

分析:虽然这里使用了两个if语句,但是它们每个的代表含义是不同的,因为有加锁的存在,两个if执行的时间间隔很有可能是特别长的,因为加锁可能会产生锁竞争,竞争就意味着某些线程就会进入阻塞状态,那么什么时候这个处于阻塞状态的线程被唤醒,就无法确认了。

这也就导致了同一个线程中的两个if语句的结果可能是截然不同的,比如 第一个if成立进入,第二个if不成立。

举个例子:

有三个线程同时调用了getInstance这个方法,通过外层的if语句进入,三个线程发生锁竞争,当其中的某个线程拿到锁之后,另外两个线程进入阻塞等待状态,之后这个拿到锁的释放了锁之后(这时已经实例化一个对象了),另外的两个线程再次发生锁竞争,两个中的某个线程拿到锁之后,进行了内层的if条件判断(刚刚这两个线程都已经判断了外层的if条件),发现instance不为空,于是就不再创建实例了。而在这三个线程之后的线程中,如果有人调用getInstance方法,就会在外层的if语句中发现instance不为空,就不再进入内部了。

这样两个if的做法,就很好的提高了效率。

当然,即使修改到这,仍然是不够的,因为这里还存在指令重排序问题。

在多线程的环境下,很可能会出现频繁的判断,这时线程不会读内存中的数据,而是会去读寄存器中的数据,可能instance的值以及发生了改变,但是线程却浑然不知,为了防止这一现象出现,我们使用volatile修饰instance变量,防止这一因为编译器优化而带来的问题。

附上代码:

class Singleton{
    private static volatile Singleton instance = null;

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

    }

}

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

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

相关文章

【涂鸦蓝牙SDK】基于涂鸦蓝牙SDK数据传输与函数接口解析

基于涂鸦蓝牙SDK数据传输与函数接口解析1.【数据初始化部分】2.【蓝牙状态机控制】3.【数据广播过程】4.【涂鸦平台申请设备以及SDK】5.【涂鸦SDK模组源码思路解析】---- 重要:5.1 数据收发5.【移植涂鸦评估】2023.1.21 本文是基于涂鸦SDK的低功耗蓝牙BLE协议的数据…

Linux创建解压后的应用程序的桌面快捷方式

下面用一个例子演示,其他应用也差不多 下载好的安装文件为.tar.xz格式,通常默认在系统的下载文件夹下(按你实际路径)。右键点击文件,在下拉框中点击提取到此处(意思就是解压)。 解压后&#xff…

DocArray 0.21.0版本发布!新增OpenSearch后端存储,支持Redis后端存储的多语言文本搜索!...

github.com/docarray/docarrayDocArray 是一个用于处理、传输和存储多模态数据的 Python 工具包。DocArray 提供便捷的多模态数据处理功能,具备基于 Protobuf 提供高性能的网络传输性能,同时也为多种向量存储方案提供统一的 API 接口。💡 Doc…

三十三、Kubernetes中Service详解、实例第三篇

1、概述 在kubernetes中,pod是应用程序的载体,我们可以通过pod的ip来访问应用程序,但是pod的ip地址不是固定的,这也就意味着不方便直接采用pod的ip对服务进行访问。 为了解决这个问题,kubernetes提供了Service资源&…

06.动态内存管理

1. 存在动态内存分配的原因我们已经掌握的内存开辟方式有:int val 20;//在栈空间上开辟四个字节 char arr[10] { 0 };//在栈空间上开辟10个字节的连续空间//写死了 //变长数组,int arr[n],变量的方式可以指定大小,并非意味着数组…

0th HPC Game小结

PART 1 - 基础知识 一、文件读取 二进制文件 mmap https://hpcgame.pku.edu.cn/demo/scow/api/proxy/relative/192.168.100.61/35515/ fread fwrite //readFILE* fi;if(fi fopen("input.bin", "rb")){fread(&p, sizeof(int), 1, fi);fread(&n,…

RabbitMQ之消息转换器

前言:大家好,我是小威,24届毕业生,曾经在某央企公司实习,目前在某税务公司。本篇文章将记录和分享RabbitMQ消息转换器的知识点。 本篇文章记录的基础知识,适合在学Java的小白,也适合复习中&…

深入理解机器学习——关联规则挖掘:基础知识

分类目录:《深入理解机器学习》总目录 许多商业企业在日复一日的运营中积聚了大量的数据。例如,食品商店的收银台每天都收集大量的顾客购物数据。下图给出一个这种数据的例子,通常称作购物篮事务(Market Basket Transaction&#…

Elasticsearch基本使用初体验01

ElasticSearch是一款非常强大的、基于Lucene的开源搜索及分析引擎;它是一个实时的分布式搜索分析引擎,它能让你以前所未有的速度和规模,去探索你的数据。 1.es的安装 工欲善其事,必先利其器;想要学es,我们…

九龙证券|磷酸铁锂电池包和铅酸电池有哪些区别?

目前,新能源汽车电动车一般用的电池有3种,铅酸蓄电池、镍氢充电电池、锂离子电池。伴随着电动车蓄电池技能工艺的升级换代,锂电池的发展壮大和应用领域日益持续上升。那么,磷酸铁锂电池包和铅酸电池有哪些差异呢?铅酸蓄…

PowerShell 美化(oh-my-posh)

文章目录PowerShell 美化一、 添加右键菜单1、 修改默认右键菜单2、 寻找安装目录3、 修改注册表二、 样式修改1、 环境安装2、 配置使用PowerShell 美化 一、 添加右键菜单 1、 修改默认右键菜单 直接使用这个命令可以将 win11 的右键菜单修改为 win10 的右键菜单&#xff1…

基础数学(三)位运算 JZ 15.位1的个数

正在刷DFS相关题的时候突然间,给我蹦出来这样一个回溯题: 401. 二进制手表 二进制手表顶部有 4 个 LED 代表 小时(0-11),底部的 6 个 LED 代表 分钟(0-59)。每个 LED 代表一个 0 或 1&#xff…

maven 解决Cannot access alimaven以及Process terminated

maven 解决Cannot access alimaven以及Process terminated 目录maven 解决Cannot access alimaven以及Process terminated方案一:用idea打开settings.xml,更正红色报错方案二:将IDEA的Maven默认版本更换成你下载的maven文件夹方案三&#xff…

单片机堆栈知识总结

堆栈 在片内RAM中,常常要指定一个专门的区域来存放某些特别的数据 它遵循顺序存取和后进先出(LIFO/FILO)的原则,这个RAM区叫堆栈。 其实堆栈就是单片机中的一些存储单元,这些存储单元被指定保存一些特殊信息,比如地址&#xff0…

DFS(二)岛屿问题合集

目录 一、 463. 岛屿的周长 二、 130. 被围绕的区域 三、 200. 岛屿数量 四、695. 岛屿的最大面积 一、463. 岛屿的周长 给定一个 row x col 的二维网格地图 grid ,其中:grid[i][j] 1 表示陆地, grid[i][j] 0 表示水域。 网格中的格子 …

Java设计模式-解释器模式、解释器模式什么回事,抽象语法树又是什么

继续整理记录这段时间来的收获,详细代码可在我的Gitee仓库SpringBoot克隆下载学习使用! 6.12 解释器模式 6.12.1 概述 思维:翻译识别机器,如解析由数字、“”、“-”号构成的合法运算序列,若将数字和字符看作结点&a…

Lesson 4.1 逻辑回归模型构建与多分类学习方法

文章目录一、广义线性模型(Generalized liner model)的基本定义二、对数几率模型与逻辑回归1. 对数几率模型(logit model)2. 逻辑回归与 Sigmoid 函数3. Sigmoid 函数性质三、逻辑回归模型输出结果与模型可解释性四、多分类学习与…

CPU缓存一致性

CPU缓存一致性写直达写回缓存一致性总线嗅探MESI协议CPU Cache通常分为三级缓存,L1Cache,L2Cache,L3Cache,级别越低的离CPU越近,访问速度越快,但同时容量越小,价格越贵。在多核的CPU中,每个核都…

今天大年三十,新年快乐,我在这里给大家整理了一下除夕的习俗,来看看吧

今天是大年三十,阿玥在这里祝大家,一来风水,二来平安,阖家欢乐,四季平安,五福临门,六六大顺,七星高照,八方来财,十全十美,新年好! 名字:不晓得 学习:python,c 主页:木有 今天给大家整理一下大年三十的习俗等小知识,就不更python啦 目录 除夕要做的事情有什么…

Meta CTO:真正的全天候轻量化AR眼镜,可能要到2030年

去年Meta发布了售价高达1500美元的VST头显Quest Pro,该头显与Meta的Quest 2等产品在定价、技术路径上有很大不同,其搭载了眼球追踪、彩色VST等更高端的功能,而产品发布后,外界对其反馈也褒贬不一。作为Pro产品线首个产品&#xff…