【Java EE初阶六】多线程案例(单例模式)

1. 单例模式

        单例模式是一种设计模式,设计模式是我们必须要掌握的一个技能;

1.1 关于框架和设计模式

        设计模式是软性的规定,且框架是硬性的规定,这些都是技术大佬已经设计好的;

        一般来说设计模式有很多种,且不同的语言会有不同的设计模式,(同时设计模式也可以理解为对编程语言的一种补充

1.2 细说单例模式

        单例 = 单个实例(对象);

        某个类,在一个线程中,只应该创建一个实例化对象(原则上不应该有多个),这时就使用单例模式,如此可以对我们的代码进行一个更严格的校验和检查。

        保证对象唯一性的方法:

        方法一,可以通过“协议约束”,写一个文档,规定这个类只能有唯一的实例,程序员在接手这个代码时,就会发现这个文档已经进行约定,其中的规定约束着程序员在创建对象时,时刻注意只能创建一个对象。

        方法二:从机器入手;让机器帮我们检查,我们期望让机器帮我们对代码中指定的类,创建类的实例个数进行检查、校验,当创建的实例个数超过我们期望个数,就编译报错。其中单例模式就是已经设计好的套路,可以实现这种预期效果。

        关于单例模式代码实现的基本方式有两种:饿汉模式和懒汉模式;

2. 饿汉模式

        饿汉模式是指创建实例的时期非常早;在类加载的时候,程序一启动,就已经创建好实例了,使用 “饿汉”这个词,就是形容创建实例非常迫切,非常早。单例模式代码如下:

class Singleton {private static Singleton instance = new Singleton();public static Singleton getInstance() {return instance;}private Singleton(){ }
}
public class TestDemo4 {public static void main(String[] args) {Singleton singleton = new Singleton();}
}

        当我们运行该代码时,系统就会报错,接下来我们详细的分析一下此处的代码; 

        这样,如果我们想new一个Singleton对象,也new不了,同时不管我们用getInstance获取多少次实例,获取的对象都是同一个对象,代码如下:

package thread;// 就期望这个类只能有唯一的实例 (一个进程中)
class Singleton {private static Singleton instance = new Singleton();public static Singleton getInstance() {return instance;}private Singleton() {}
}public class ThreadDemo26 {public static void main(String[] args) {// Singleton s = new Singleton();Singleton s = Singleton.getInstance();Singleton s2 = Singleton.getInstance();System.out.println(s == s2);}
}

        结果如下:

3. 懒汉模式

        和饿汉模式不一样的是,懒汉模式创建实例的时机比较晚,没饿汉创建实例那么迫切,只有第一次使用这个类时,才会创建实例,代码如下:

class SingletonLazy {private static SingletonLazy instance = null;public static SingletonLazy getInstance() {if(instance == null) {instance = new SingletonLazy();}return instance;}private SingletonLazy() { }
}
public class TestDemo5 {public static void main(String[] args) {}
}

        下面为代码图解分析:

        和饿汉模式的区别就是没那么迫切创建实例,等需要调用这个类的时候才创建一个实例,而饿汉模式是有了这个类就创建出实例。

        懒汉模式的优点:有的程序,要在一定条件下,才需要进行相关的操作,有时候不满足这个条件,也就不需要完成这个操作了,如此哦·就把这个操作省下来了。

4. 两种模式关于线程安全

4.1 饿汉模式

        线程安全;

        对于饿汉模式来说,上图所示通过调用getinstance方法来返回instance对象,本质上来说是读操作;

        当有多个线程,同时并发执行,调用getInstance方法,取instance,这时线程是安全的,因为只涉及到读,多线程读取同一个变量,是线程安全的。而instance很早之前就已经创建好了,不会修改它,一直也只有这一个实例,也不涉及写的操作。

4.2 懒汉模式

        线程不安全;

        在懒汉模式中,条件判定和返回时是读操作,new一个对象是写操作;

       我们只有调用getInstance方法后,就会创建出实例来,如果多个线程同时调用这个方法,此时SingletonLazy类里面的instance都为null,那么这些线程都会new对象,就会创建多个实例。这时,就不符合我们单例模式的预期了,所以,这个代码是线程不安全的。

        线程不安全的直接原因,就是 “写” 操作不是原子的。

4.3 解决懒汉模式的线程安全问题

4.3.1 把写操作打包成原子

        因为多线程并发执行的时候,可能读到的都是instance == null,所以会创建多个实例,那我们就给它加锁,让它在创建实例的时候,只能创建一个,加锁代码如下:

class SingletonLazy {private static Object locker = new Object();private static SingletonLazy instance = null;public static SingletonLazy getInstance() {synchronized (locker) {if(instance == null) {instance = new SingletonLazy();}}return instance;}private SingletonLazy() { }
}

        以上操作虽然将写操作打包成了一个原子,但是新的问题也出现了;

4.3.2 去除冗余操作

         上述操作加上了还是有问题:如果已经创建出实例了,我们还有加锁来判断它是不是null吗,加锁这些操作也是要消耗硬件资源的,没有必要为此浪费资源空间,如果已经不是null了,我们就想让它直接返回,不再进行加锁操作,代码修改如下:

class SingletonLazy {private static Object locker = new Object();private static SingletonLazy instance = null;public static SingletonLazy getInstance() {if (instance == null) {synchronized (locker) {if (instance == null) {instance = new SingletonLazy();}}}return instance;}private SingletonLazy() { }
}

        代码图解分析两个判断语句的是目的意义:

4.3.3 指令重排序的问题

        指令重排序:指令重排序也是编译器的一种优化,在保证原代码的逻辑不变,调整原代码的指令执行顺序,从而让程序的执行效率提高。

        保证原代码的逻辑不变,改变原有指令的顺序,从而提高代码的执行效率,其中这个代码,就存在着指令重排序的优化,如下图代码:

该语句原本指令执行顺序:

        1、去内存申请一段空间

        2、在这个内存中调用构造方法,创建实例

        3、从内存中取出地址,赋值给这个实例instance。

指令重排序后的顺序:1, 3 , 2;按照指令重排序后的代码执行逻辑就变成了下面所示:

        假设有两个线程,现在执行顺序如下图所示:

        因为指令重排序后,先去内存申请一段空间,然后是赋值给instance,那这时,instance就不是null了,第二个线程不会进入到if语句了,直接返回instance,可是instance还没有创建出实例,这样返回肯定是有问题的,如此也就线程不安全了。

        解决方案:

        给instance这个变量,加volatile修饰,强制取消编译器的优化,不能指令重排序,同时也排除了内存可见性的问题。

        加volatile后的代码如下:


 

class SingletonLazy {private static Object locker = new Object();private static volatile SingletonLazy instance = null;public static SingletonLazy getInstance() {if (instance == null) {synchronized (locker) {if (instance == null) {instance = new SingletonLazy();}}}return instance;}private SingletonLazy() { }
}

        至此,我们才算解决掉懒汉模式关于线程安全的所有问题;

4.4 懒汉模式线程安全的代码

package thread;// 懒汉的方式实现单例模式.
class SingletonLazy {// 这个引用指向唯一实例. 这个引用先初始化为 null, 而不是立即创建实例private volatile static SingletonLazy instance = null;private static Object locker = new Object();public static SingletonLazy getInstance() {// 如果 Instance 为 null, 就说明是首次调用, 首次调用就需要考虑线程安全问题, 就要加锁.// 如果非 null, 就说明是后续的调用, 就不必加锁了.if (instance == null) {synchronized (locker) {if (instance == null) {instance = new SingletonLazy();}}}return instance;}private SingletonLazy() { }
}public class ThreadDemo27 {public static void main(String[] args) {SingletonLazy s1 = SingletonLazy.getInstance();SingletonLazy s2 = SingletonLazy.getInstance();System.out.println(s1 == s2);}
}

        结果如下:

ps:本次的内容就到这里了,如果感兴趣的话,就请一键三连哦!!!

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

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

相关文章

谈谈Mongodb insertMany的一些坑

概述 Mongodb提供了多种方法向集合中插入数据 插入一条数据 db.collection.insertOne() 插入多个文档 db.collection.insertMany() 更新集合中不存在的文档数据,指定{upsert: true}时插入数据 db.collection.updateOne() db.collection.updateMany() db.coll…

Go语言之父:开源14年,Go不止是编程语言,究竟做对了哪些?

提及编程语言,2023 年,除了老牌的 C 和新晋之秀 Rust 热度最高之外,就要数 Go 了。 从 2009 年由 C 语言获取灵感而发布,到如今风靡已久的高性能语言,Go 已经走过了 14 个年头。 “Go是一个项目,不只是一门…

基于ssm的智慧社区电子商务系统+vue论文

目 录 目 录 I 摘 要 III ABSTRACT IV 1 绪论 1 1.1 课题背景 1 1.2 研究现状 1 1.3 研究内容 2 2 系统开发环境 3 2.1 vue技术 3 2.2 JAVA技术 3 2.3 MYSQL数据库 3 2.4 B/S结构 4 2.5 SSM框架技术 4 3 系统分析 5 3.1 可行性分析 5 3.1.1 技术可行性 5 3.1.2 操作可行性 5 3…

Go语言中的init函数的执行时机

init函数的执行时机 这个涉及到 init 函数的作用和执行顺序相同个文件和不同文件中以及在不同的包中init的执行顺序go文件初始化的顺序 一、init 函数的作用和执行顺序 作用 init 函数是用于程序执行前做包的初始化的函数,比如初始化包里面的一些变量等等通常在…

HTML5大作业-精致版个人博客空间模板源码

文章目录 1.设计来源1.1 博客主页界面1.2 博主信息界面1.3 我的文章界面1.4 我的相册界面1.5 我的工具界面1.6 我的源码界面1.7 我的日记界面1.8 我的留言板界面1.9 联系博主界面 2.演示效果和结构及源码2.1 效果演示2.2 目录结构2.3 源代码 源码下载 作者:xcLeigh …

从零学Java 包装类

Java 包装类 文章目录 Java 包装类1 什么是包装类?2 为什么需要包装类?3 包装类对应4 包装类的基本操作4.1 装箱4.2 拆箱4.3 自动装箱/拆箱面试题整数缓冲区 5 包装类的类型转换5.1 字符串转基本数据类型5.2 基本数据类型转字符串5.2.1 方法一: 号字符串拼接5.2.2 方法二: 使…

在MS中基于perl脚本实现氢键统计

氢原子与电负性大的原子X以共价键结合,若与电负性大、半径小的原子Y(O F N等)接近,在X与Y之间以氢为媒介,生成X-H…Y形式的一种特殊的分子间或分子内相互作用,称为氢键。 氢键通常是物质在液态时形成的&…

【华为OD真题 Python】解密犯罪时间

文章目录 题目描述输入描述输出描述示例1输入输出说明示例1输入输出说明示例2输入输出说明备注代码实现题目描述 警察在侦破一个案件时,得到了线人给出的可能

第1章 线性回归

一、基本概念 1、线性模型 2、线性模型可以看成:单层的神经网络 输入维度:d 输出维度:1 每个箭头代表权重 一个输入层,一个输出层 单层神经网络:带权重的层为1(将权重和输入层放在一起) 3、…

数据库设计——DML

D M L \huge{DML} DML DML:数据库操作语言,用来对数据库中的数据进行增删改查。 增(INSERT) 使用insert来向数据库中增加数据。 示例: -- DML : 数据操作语言 -- DML : 插入数据 - insert -- 1. 为 tb_emp 表的 us…

Kubernetes二进制部署 单节点

一、环境准备 k8s集群master1:192.168.229.90 kube-apiserver kube-controller-manager kube-scheduler etcd k8s集群node1: 192.168.229.80 kubelet kube-proxy docker flannel k8s集群node2: 192.168.229.70 kubelet kube-proxy docker flannel 至少2C2G 常见的k…

Flutter3.X基础入门教程(2024完整版)

Flutter介绍: Flutter是谷歌公司开发的一款开源、免费的UI框架,可以让我们快速的在Android和iOS上构建高质量App。它最大的特点就是跨平台、以及高性能。 目前Flutter已经支持 iOS、Android、Web、Windows、macOS、Linux的跨平台开发。 教程所讲内容支持…

独立式键盘控制步进电机实验

#include<reg51.h> //包含51单片机寄存器定义的头文件 sbit S1P1^4; //将S1位定义为P1.4引脚 sbit S2P1^5; //将S2位定义为P1.5引脚 sbit S3P1^6; //将S3位定义为P1.6引脚 unsigned char keyval; //储存按键值 unsigned char ID; …

bat批处理文件_命令汇总(2)

文章目录 1、换行2、返回上一级目录cd..3、隐藏指令回显echo off4、开启指令回显echo on5、用关闭echo off指令本身的回显6、echo提示信息 1、换行 cd.. echo. echo. echo. pause2、返回上一级目录cd… 3、隐藏指令回显echo off echo off echo hello1 echo hello2 pause4、开…

tomcat session cookie值设置逻辑

tomcat session cookie 值设置&#xff0c;tomcat jsessionid设置 ##调用request.getSession() Controller RequestMapping("/cookie") public class CookieController {RequestMapping("/tomcatRequest")ResponseBodypublic String tomcatRequest(HttpS…

软件测试|什么是Python构造方法,构造方法如何使用?

构造方法&#xff08;Constructor&#xff09;是面向对象编程中的重要概念&#xff0c;它在创建对象时用于初始化对象的实例变量。在Python中&#xff0c;构造方法是通过特殊的名称__init__()来定义的。本文将介绍Python构造方法的基本概念、语法和用法。 什么是构造方法&…

轻松获取CHATGPT API:免费、无验证、带实例

免费获取和使用ChatGPT API的方法 快速开始&#xff1a;视频教程 章节一&#xff1a;GPT-API-Free开源项目介绍 GPT-API-Free 是一个开源项目&#xff0c;它提供了一个中转API KEY&#xff0c;使用户能够调用多个GPT模型&#xff0c;包括gpt-3.5-turbo、embedding和gpt-4。这…

记一次服务器被入侵的排查过程

起因 阿里云安全中心报告了告警信息&#xff0c;同时手机短信、邮件、电话也接收到了来自阿里云的风险通知&#xff0c;感觉这方面阿里云还是不错。 排查及解决过程 这条wget指令究竟是怎么被运行的 我无法定位到攻击人员是通过什么样的方式让我的java程序执行了wget这条指…

JavaScript中绑定事件的方法

在JavaScript中&#xff0c;绑定事件的方法主要有三种&#xff1a; 直接在HTML元素中通过onclick&#xff0c;onload等属性直接绑定事件处理函数。使用DOM0级事件绑定&#xff0c;即直接在JavaScript代码中通过element.onclick function()或者element.addEventListener(click…

地平面--高速布线

https://baijiahao.baidu.com/s?id1764139038516816855&wfrspider&forpc 概念 回顾传输线&#xff0c;由任意两条有一定长度的导线组成&#xff0c;一条为信号路径&#xff0c;一条为返回路径。基本电路理论告诉我们&#xff0c;信号是由电流传播的&#xff0c;明确的…