java EE初阶 — 多线程案例单例模式

news2024/11/24 0:27:45

文章目录

  • 1单例模式主要模式
    • 1.1 饿汉模式
    • 1.2 懒汉模式
  • 2 单例模式安全性问题

1单例模式主要模式

在某些场景中,有些特定的类只能输出一个实例(对象),不应该创建多个实例,此时就可以使用 单例模式
使用了单例模式后,就很难创建多个实例。

单例模式 主要介绍两种常见的方式:饿汉模式懒汉模式

1.1 饿汉模式

先来看一段代码。

package thread;

//饿汉模式 - 单例模式实现
//此处要保证这个类只能创建一个实例
class Singleton {
    //创建实例
    private static Singleton instance = new Singleton();

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

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

public class ThreadDemo22 {
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2);
    }
}




s1 和 s2 相等说明这是一个实例,而且此时使用 new 一个实例 程序则会报错。



如果一个饿了很久的人看到吃的,那么他就会特别急切的去吃了这个东西。

类加载阶段,就把实例创建出来了。(累加载是比较靠前的阶段)
这种效果,就会给人一种 “特别急切” 的感觉。

1.2 懒汉模式

先来看一段代码。

package thread;

class SinglentonLazy {
    private static SinglentonLazy instance = null;

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

    private SinglentonLazy() {}
}

public class ThreadDemo23 {
    public static void main(String[] args) {
        SinglentonLazy s1 = SinglentonLazy.getInstance();
        SinglentonLazy s2 = SinglentonLazy.getInstance();
        System.out.println(s1 == s2);
    }
}




s1 和 s2 相等说明这是一个实例。

懒汉模式中的实例并不是类加载的时候创建了,而是真正第一次使用的时候才会去创建。
如果不会使用,就不创建。

就好比生活中的拖延症,不到最后时刻绝不行动。

在效率方面。懒汉模式要更胜一筹。

2 单例模式安全性问题

上述讲的 饿汉模式懒汉模式 如果在多线程的环境下调用 getInstance ,是否是线程·安全的。

懒汉模式有读有写。


饿汉模式这里是涉及到了 “读操作”。


这两种模式只有一种是安全的,那不安全的是哪一种呢?



注意 new 本质上也是也是2多个指令,此处暂时视为一个整体,不影响对程序的分析。

如何使用加锁让 懒汉模式 变成线程安全的?


下面这种方式是不行的。



下面这种方式是可以的。

package thread;

class SinglentonLazy {
    private static SinglentonLazy instance = null;

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

    private SinglentonLazy() {}
}

public class ThreadDemo23 {
    public static void main(String[] args) {
        SinglentonLazy s1 = SinglentonLazy.getInstance();
        SinglentonLazy s2 = SinglentonLazy.getInstance();
        System.out.println(s1 == s2);
    }
}

在这里插入图片描述



此处的 t2 load 得到的结果是 t1 修改过后的结果。(得到的是一个非 null 的值)
因此 t2 就不会触发 if 条件,也就不会再创建新的对象了。
而是直接返回现有对象了。

执行到此处代码还是会有问题。

每次 getInstance 都需要加锁,而加锁是要有开销的,这里就需要仔细考虑需不需要每次都加锁?

仔细想想就会发现。这里的加锁只是在 new 出对象之前加上的,是有必要的。
一旦对象 new 完了之后,后序调用 getInstance 。此时 instance 的值一定是非空的。
因此就会直接触发 return ,相当于是一个比较操作,一个是返回操作。
这两个操作都是读操作,此时不加锁也没事。

基于上面的情况,可以加上一个判定。
如果对象还没创建,才加锁;如果对象已经创建就不加锁了。

下面是部分代码

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

此时就不再是无脑加锁了。而是在满足了条件时候才加锁。

即便是优化到了现在这个地步也还是会有 内存可见性 问题。

假设有很多的线程,都去进行 getInstance ,这个时候,是否会有被优化的风险呢?
(只有第一次读才是真正的读了内存,后续都是读寄存器/cache)
这就是内存可见性问题。

这里还会涉及到 指令重排序问题

instance = new Singleton(); 会被拆分成三个步骤:

  1. 申请内存空间。
  2. 调用构造方法,把这个内存空间初始化成一个合理的对象。
  3. 把内存空间的地址赋值给 instance 引用。

如果是正常的情况下是会按照 1 2 3 的顺序来执行的,
但是编译器为了提示效率还会进行指令重排序,也就是调整代码的执行顺序。

此时 1 2 3 就可能变成了 1 3 2 。(单线程 1 2 3 和 1 3 2 没有去区别)

但是如果是多线程环境下就回出现问题了。

假设 t1 是按照 132 的顺序来执行的。
t1 执行到 1 3 之后,执行 2 之前会被切除 CPU ,t2 来执行。

(当 t1 执行完 3 之后,t2 看起来此处的引用就非空了)
此时此刻 t2 就相当于是直接返回了 instance 引用。并且可能会尝试使用引用中的属性。

但是由于 t1 中的 2 操作还没完全执行完呢,t2 拿到的是非法的对象,还没构造完成的不完整的对象。

解决办法:volatile

两个功能:

  • 解决内存可见性
  • 禁止指令重排序

部分代码

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

    private SinglentonLazy() {}

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

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

相关文章

5款轻量级小软件,第一款更是近期必备!

今天的主题是简洁,轻便,都是轻量级的小软件,界面都是非常简洁,而且无广告的。 1.自动抢火车票工具——12306Bypass 12306Bypass是一款专用于帮助用户抢购火车车票的工具,春运马上就到了,又到了抢票回家的…

Docker数据卷操作

1. 为什么使用数据卷 卷是在一个或多个容器内被选定的目录,为docker提供持久化数据或共享数据,是docker存储容器生成和使用的数据的首选机制。对卷的修改会直接生效,当提交或创建镜像时,卷不被包括在镜像中。 总结为两个作用&am…

功率放大器在压电传感器矩形阵列成像研究中的应用

实验名称:激光和压电传感器密集型矩形阵列成像质量的比较分析 研究方向:Lamb波、无损检测、缺陷成像和定位 测试目的: 将密集型矩形阵列分别与压电传感器检测技术和激光检测技术相结合,利用幅值成像和符号相干因子成像实现对铝板结…

vector模拟实现下篇及迭代器失效和深浅拷贝问题详解

文章目录1:构造函数1.1默认构造函数1.2迭代器构造1.3用n个val构造1.4拷贝构造2:operator3:析构函数和clear4:迭代器失效问题4.1:删除偶数深浅拷贝1:构造函数 1.1默认构造函数 vector():_start(nullptr),_end(nullptr),_endofstorage(nullptr){}1.2迭代器构造 template<clas…

手动安装Kylin5.0版本的过程

官方文档 https://kylin.apache.org/目前kylin3,4版本是有docker版本和安装包的,5.0只有docker没有安装包 安装包 https://kylin.apache.org/download/安装kylin5.0 Kylin5.0文档拉取镜像 docker pull apachekylin/apache-kylin-standalone:5.0.0运行镜像 docker run -d \ …

linux-jdk、nginx

一、安装nginx Nginx是一个web服务器也可以用来做负载均衡及反向代理使用&#xff0c; 目前使用最多的就是负载均衡&#xff0c;这篇文章主要介绍了centos8 安装 nginx Nginx是一种开源的高性能HTTP和反向代理服务器&#xff0c;负责处理Internet上一些最大站点的负载。 它…

数据结构——重点代码汇总

顺序表 设计算法&#xff0c;从顺序表L中删除值为x的元素。要求算法的时间复杂度为O(n)&#xff0c;空间复杂度为O(1)。设计算法&#xff0c;判断一个字符串是否是回文。如abc3cba是回文序列&#xff0c;而1331不是回文序列。从顺序存储结构的线性表a中删除第i个元素起的k个元…

SuperMap GIS 倾斜摄影数据处理 QA

一、倾斜摄影数据简介 倾斜摄影&#xff08;Oblique photography&#xff09;是指由一定倾斜角度的航摄相机所获取的影像。倾斜摄影技术是国际摄影测量领域近十几年发展起来的一项高新技术&#xff0c;该技术通过从一个垂直、四个倾斜、五个不同的视角同步采集影像&#xff0c…

深度学习训练营之天气识别P3

深度学习训练营之天气识别原文链接环境介绍前置工作设置GPU导入数据数据查看数据预处理加载数据可视化数据检查数据配置数据集prefetch()功能详细介绍&#xff1a;构建CNN网络编译模型训练结果可视化原文链接 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记…

卫龙上市首日破发:高瓴、红杉、腾讯等账面亏损,刘卫平为董事长

12月15日&#xff0c;卫龙美味全球控股有限公司&#xff08;下称“卫龙”&#xff0c;HK:09985&#xff09;在港交所上市。本次上市&#xff0c;卫龙的发行价格为10.56港元/股&#xff0c;募资总额约为10.18亿港元&#xff0c;募资净额约为8.99亿港元。 上市首日&#xff0c;卫…

Web3中文|NFT如何助力项目进入Web3?

自NFT流行以来&#xff0c;一直有人将这些由区块链驱动的代币视作贯彻人类精神的最终疗法。 但是NFT真的都存储在区块链上了吗&#xff1f;如果是这样的话&#xff0c;怎么还会出现百万NFT被盗的事件呢&#xff1f; 如果你也想过这些问题&#xff0c;那么请相信我&#xff0c…

在现有项目里面添加 TSX 并编写组件过程记录

首先需要安装编译支持和 vite 支持插件 ## babel 基础插件 yarn add vue/babel-plugin-jsx -D## 项目用 vite 构建的就需要按照这个 yarn add vitejs/plugin-vue-jsx -D 使用插件 按照 babel-plugin-jsx 的指引在 babel 配置项中启用插件&#xff1a; {"plugins":…

Linux操作系统常见问题汇总

1.系统启动流程。 uboot -> kernel -> 根文件系统。 uboot第一阶段属于汇编阶段&#xff1a; 定义入口&#xff08;start.S&#xff09;&#xff1a;uboot中因为有汇编阶段参与&#xff0c;因此不能直接找main.c。 设置异常向量&#xff1a;当硬件发生故障的时候CPU会…

K8s Dashboard 部署

1、下载 Dashboard 的 yaml 文件 点击链接下载 YAML 文件 2、源码包中 yaml 文件在哪里 3、修改 yaml 文件 vim recommended.yaml... kind: Service apiVersion: v1 metadata:labels:k8s-app: kubernetes-dashboardname: kubernetes-dashboardnamespace: kubernetes-dashboard…

Java web 2022跟学尚硅谷(十) 后端基础 书城

Java web 2022跟学尚硅谷十 后端基础 书城验证码kaptcha和缓存cookie简单了解cookie步骤简单创建cookie的样例代码CookieServlet01hello.html页面结果Cookie保存结果第二次请求cookie的APIKaptcha验证码使用步骤显示效果验证码的校验相关类KaptchaServlet01书城1.2正则表达式正…

C语言经典题目—单链表求和

练习的题目<单链表求和>题目难度较小。 1、题目描述 小明输入了一个长度为 n 的数组&#xff0c;他想把这个数组转换成链表&#xff0c;链表上每个节点的值对应数组中一个元素的值&#xff0c;然后遍历链表并求和各节点的值。输入描述&#xff1a; 第一行输入一个正整数…

Linux下内存空间分配、物理地址与虚拟地址映射

一、Linux内核动态内存分配与释放 1.1 kmalloc函数 Kmalloc分配的是连续的物理地址空间。如果需要连续的物理页&#xff0c;可以使用此函数&#xff0c;这是内核中内存分配的常用方式&#xff0c;也是大多数情况下应该使用的内存分配方式。 传递给函数的最常用的标志是GTP_A…

火山引擎DataTester:无需研发人力,即刻开启企业A/B实验

近日&#xff0c;火山引擎A/B测试平台—— DataTester 对产品内A/B实验的“可视化编辑器”进行了新的升级&#xff0c;对交互、预览、Xpath的层次结构视图等能力均做了优化。 据介绍&#xff0c;火山引擎DataTester的可视化编辑器&#xff0c;可以让用户无需编写任何代码&#…

基于java+springboot+mybatis+vue+mysql的CSGO游戏比赛赛事管理系统

项目介绍 CSGO赛事管理系统利用网络沟通、计算机信息存储管理&#xff0c;有着与传统的方式所无法替代的优点。比如计算检索速度特别快、可靠性特别高、存储容量特别大、保密性特别好、可保存时间特别长、成本特别低等。在工作效率上&#xff0c;能够得到极大地提高&#xff0…

基于JAVA+SpringMVC+Mybatis+Vue+MYSQL的大健康老年公寓管理系统

项目介绍 本系统采用java语言开发&#xff0c;后端采用ssm框架&#xff0c;前端采用vue技术&#xff0c;数据库采用mysql进行数据存储。 管理员后台页面&#xff1a; 功能&#xff1a;主页、个人中心、护理人员管理、收费标准管理、接待登记管理、房间信息管理、床位信息管理…