[Java EE] 多线程(五):单例模式与阻塞队列

1. 单例模式

单例模式是校招中最长考的设计模式之一,首先我们来谈一谈什么是设计模式:

设计模式就好像象棋中的棋谱一样,如果红方走了什么样的局势,黑方就有一定地固定地套路,来应对这样的局势,按照固定地套路来,可以保证在该局势下不会吃亏.
在这里插入图片描述
软件开发也是同样的道理,有很多的问题场景,针对这些问题场景,一些编程界的大佬就总结出了一套固定的解决模版,只要按着这个套路来,就会避免出现各种各样的bug.

软件设计中的单例模式就是保证每个程序运行的过程中只存在一份实例,而不会创建多个实例.这种设计模式在我们的日常开发中比比皆是,使用单例模式也可以大大提高程序的效率,下面我们来举个例子:
假如服务器中药从硬盘上读取100G的固定数据到内存中,这样的操作肯定是通过一个类来封装,这样的类最好是单例的,因为数据一样,在创建多个实例的时候,就会对系统资源开销很大,而且多次读取同样数据这样的操作也没必要,让他们共享同一份数据就可以了.

但是如何保证每个类只创建一个实例出来呢?这可不是程序员口头保证就行,只要是人去做,都不靠谱,我们应该把这样的操作交给计算机来处理,需要让编译器来帮我们做一个强制的检查.

通常的单例模式有以下两种,懒汉模式和饿汉模式:

1.1 饿汉模式

所谓饿汉模式,就突出一个字:,就好像一个饿汉被饿了好几天想要得到食物一样.我们需要在JVM启动的时候,即类刚刚加载的时候,就创建这个类的实例.其次,我们为了该类只创建一个实例,防止通过构造方法来创建多余的实例,所以我们把构造方法设置为私有.之后要想获取这个类的实例,我们可以通过一个普通方法来获取类中创建的这个实例.代码如下:

/*** 饿汉模式*/
class Singleton{public static Singleton instance = new Singleton();//在加载类的时候就创建实例private Singleton(){}//通过把构造方法设计为private修饰的来避免调用构造方法public static Singleton getInstance(){return instance;}
}

在多线程情况下,饿汉模式仍然是线程安全的:因为在创建这个类的时候,这个实例就已经有了,它的创建比主线程的启动都要早,所以在主线程中启动其他线程的时候,只需要读取已经创建好的线程就可以,我们前面说到,针对同一个变量进行修改的时候容易产生线程安全问题,但是现在只有读取操作,所以不存在线程安全问题.

1.2 懒汉模式

所谓懒汉模式,就突出一个字:,在第一次调用这个类的实例的时候才创建实例.如果没有被创建,引用一直为null.
单线程情况:

/*** 懒汉模式*/
class SingletonLazy{Object object = new Object();public static SingletonLazy instance = null;private SingletonLazy(){}public static SingletonLazy getInstance(){if (instance == null){//判断实例是否被创建过,如果被创建过,就不创建了,直接返回instance = new SingletonLazy();}return instance;}
}

多线程情况:
上述代码虽然说在单线程情况下不会出现什么bug,但是在多线程的情况下,就会难免出现bug,下面我们就通过画图的方式解释以下为什么会出现bug.
在这里插入图片描述
在线程1和线程2创建实例的时候,线程2的条件判断有可能在线程1创建实例执之前,此时线程1和线程2都会创建实例,从而违背了单例模式的初衷.这里产生线程安全问题的原因是操作不符合原子性.
那么为了避免在多线程中的安全问题,我们可以通过加锁的方式,把创建对象这个操作打包成一个原子的操作.

/*** 懒汉模式*/
class SingletonLazy{Object object = new Object();public static SingletonLazy instance = null;private SingletonLazy(){}public static SingletonLazy getInstance(){synchronized (instance.object){if (instance == null){//防止多个线程同时创建多个对象instance = new SingletonLazy();}}return instance;}
}

但是上述代码在一些方面还是显得有些赘余:

  1. 如果线程比较多,会加重锁竞争,在最初调用getInstance的时候会存在线程安全问题,但是后续调用,就只是读取操作,并不存在线程安全问题,所以此时上锁就是赘余,上锁本身也是一个开销比较大的操作.
  2. 存在内存可见性问题和指令重排序问题导致读取instance的偏差.
    为了解决上述问题,我们对上述代码进行优化:
/*** 懒汉模式*/
class SingletonLazy{Object object = new Object();public static volatile SingletonLazy instance = null;//加上volatile方式指令重排序和内存可见性引起的bugprivate SingletonLazy(){}public static SingletonLazy getInstance(){if (instance == null){//节省上锁的开支synchronized (instance.object){if (instance == null){//防止多个线程同时创建多个对象instance = new SingletonLazy();}}}return instance;}
}

这里两层判断的条件虽然是一样的,但是它们所起到的作用,却是大相径庭:
第一层判断是为了创建好实例之后,在后续获取实例的时候重复加锁,已节省系统开销.
第二层判断是为了多个线程如果都在创建好实例之前运行,防止这几个线程同时创建多个实例.

举例说明:
有请助教:荧,温迪,莱欧斯利,原神其他男性角色
现在温迪和莱欧斯利(两个线程)都知道了荧没有男朋友(没有创建实例),他们都突破了第一层if,于是开始竞争统一把锁.
在这里插入图片描述
假如现在风神巴巴托斯(线程1)更胜一筹:温迪就通过了里层的if,成为了荧的男朋友(创建了实例):
在这里插入图片描述
现在莱欧斯利(线程2)拿到了锁,但是被里层if排除在了外面,因为荧已经有了男朋友(实例已经创建了).
在这里插入图片描述
其他人听说了荧已经有了男朋友(已经创建了实例),纷纷散去了(被外层if排除在了外面).
在这里插入图片描述
在这里插入图片描述

2. 阻塞队列

2.1 什么是阻塞队列

阻塞队列是⼀种特殊的队列.也遵守"先进先出"的原则.
阻塞队列能是⼀种线程安全的数据结构(但是像我们前面学习的普通队列和优先级队列都是线程不安全的),并且具有以下特性:
• 当队列满的时候,继续⼊队列就会阻塞,直到有其他线程从队列中取⾛元素.
• 当队列空的时候,继续出队列也会阻塞,直到有其他线程往队列中插⼊元素.
阻塞队列的⼀个典型应⽤场景就是"⽣产者消费者模型".这是⼀种⾮常典型的开发模型.

拓展:消息队列
还有一种队列,叫做消息队列,首先通过topic这样的参数,对数据进行归类,出队列的时候,指定topic,每个topic下的所有数据先进先出,消息队列往往也带有阻塞特性.由于这种队列实在是太好用了,在部署服务器的时候,一般对这种队列会进行单独部署.

2.2 标准库中的阻塞队列

在Java标准库中内置了阻塞队列.如果我们需要在⼀些程序中使⽤阻塞队列.直接使⽤标准库中的即
可.
• BlockingQueue是⼀个接⼝.真正实现的类是LinkedBlockingQueue / ArrayBlockingQueue / PriorityBlockingQueue.
put方法用于阻塞式的入队列,take用于阻塞式的出队列.
• BlockingQueue也有offer,poll,peek等⽅法,但是这些⽅法不带有阻塞特性.

2.3 生产者消费者模型

⽣产者消费者模式就是通过⼀个容器来解决⽣产者和消费者的强耦合问题
⽣产者和消费者彼此之间不直接通讯, 而通过阻塞队列来进行通讯所以⽣产者⽣产完数据之后不⽤等待消费者处理,直接扔给阻塞队列,消费者不找⽣产者要数据,⽽是直接从阻塞队列⾥取.
在这里插入图片描述
生产者和消费者模型有以下好处:

  1. 阻塞队列就相当于⼀个缓冲区,平衡了⽣产者和消费者的处理能⼒.(削峰填谷)
    场景: 天猫11·11秒杀商品,在双十一的零点,阿里巴巴的服务器会接收到大量的支付请求,但是这样请求又是复杂的,很有可能服务器就被这样的一波请求冲垮了,这时候便会用到阻塞队列,让服务器慢慢处理这些指令.
    这样做可以有效进⾏"削峰",防⽌服务器被突然到来的⼀波请求直接冲垮.
    在这里插入图片描述

  2. 阻塞队列也能使⽣产者和消费者之间解耦.
    如果直接连接,服务器A和服务器B之间的逻辑关联会非常强,如果A或者B出现了bug或者出现了修改,另一个服务器就会被牵连到.这样的耦合性就比较高.我们希望通过阻塞队列来降低耦合性.让两个服务器和阻塞队列关联.

举例:过年包饺子
有请助教:空,荧
包饺子需要两步,擀皮,包馅,假如荧擀皮(生产者),空包馅(消费者),荧擀完皮会放在他们中间的盖帘上(阻塞队列).
在这里插入图片描述
假如荧擀皮慢,空包馅快,空在盖帘空的时候,空就会阻塞等待.
假如空包馅慢,荧擀皮快,空在盖帘满的时候,荧就会阻塞等待.

下面我们来用代码实现一个生产者消费者模型:

import java.util.concurrent.LinkedBlockingQueue;/*** 生产者与消费者模型*/
public class Demo23 {public static void main(String[] args) {BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>();Thread thread = new Thread(()->{int count = 1;while (true){try {blockingQueue.put(count);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("put:" + count);count++;try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread thread1 = new Thread(()->{int num = 0;while (true){try {num = blockingQueue.take();//当队列为空的时候就阻塞等待} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("take:" + num);}});thread.start();thread1.start();}
}

2.4 阻塞队列的实现

  • 通过"循环队列"的方式实现.
    循环队列描述
  • 使用synchronized进行加锁控制.
  • put插⼊元素的时候,判定如果队列满了,就进⾏wait.(注意,要在循环中进⾏wait.被唤醒时不⼀定
    队列就不满了,因为还有可能是其他的线程的interrupt唤醒了该线程
    ).
  • take取出元素的时候,判定如果队列为空,就进⾏wait.(也是循环wait)
/*** 实现阻塞队列*/
public class Block {public int size = 0;public int tail = 0;public int head = 0;public int[] item = new int[10];//默认循环队列的容积是10public int take() throws InterruptedException {//取队列元素synchronized (this){int ret = 0;while (size == 0){this.wait();}ret = item[head];//处队头元素head = (head+1)%item.length;size--;this.notify();//当出了一个元素之后,唤醒put的wait()return ret;}}public void put(int num) throws InterruptedException {synchronized (this){while (size == item.length){//队列满,阻塞等待this.wait();}item[tail] = num;tail = (tail+1)%item.length;size++;this.notify();//添加一个元素之后,唤醒take的阻塞}}
/**
*开始测试
**/public static void main(String[] args) {Block block = new Block();Thread thread = new Thread(()->{int num = 1;while (true){try {block.put(num);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("put:"+num);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}num++;}});Thread thread1 = new Thread(()->{int ret = 0;while (true){try {ret = block.take();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("take:"+ret);}});thread.start();thread1.start();}
}

测试结果:
在这里插入图片描述

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

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

相关文章

Linux基础-socket详解、TCP/UDP

文章目录 一、Socket 介绍二、Socket 通信模型三、Socket 常用函数1 创建套接字2 绑定套接字3、监听连接4、接受连接5、接收和发送数据接收数据发送数据 6、关闭套接字 四、Socket编程试验1、源码server.cclient.c 2、编译&#xff1a;3、执行结果 五、补充TCP和UDP协议的Socke…

OpenAI 新推出 AI 问答搜索引擎——SearchGPT 震撼登场

您的浏览器不支持 video 标签。 OpenAI-SearchGPT 近日&#xff0c;OpenAI 曝光了自己的一款令人瞩目的 AI 问答搜索引擎——SearchGPT。这款搜索引擎带来了全新的搜索体验&#xff0c;给整个行业带来了巨大的压力。 SearchGPT 支持多种强大的功能。首先&#xff0c;它能够通过…

分布式与一致性协议之Raft算法(三)

Raft算法 如何复制日志 你可以把Raft算法的日志复制理解成一个优化后的二阶段提交(将二阶段优化成了一阶段)。优化后减少了一半的往返消息&#xff0c;也就是降低了一半的消息延迟&#xff0c;那日志复制的具体过程又是什么呢&#xff1f; 首先&#xff0c;领导者进入第一阶段…

【Redis 开发】多级缓存,本地进程缓存Caffeine

多级缓存 多级缓存本地进程缓存CaffeineCaffeine三种缓存驱逐策略 多级缓存 Redis处理并发的能力是非常强大的&#xff0c;但是tomcat的支持并发的能力跟不上Redis的性能&#xff0c;导致整体性能的下降 Redis缓存失效时&#xff0c;会对数据库产生冲击&#xff0c;之间再无屏…

LeetCode1017题:负二进制转换(原创)

【题目描述】 给你一个整数 n &#xff0c;以二进制字符串的形式返回该整数的 负二进制&#xff08;base -2&#xff09;表示。注意&#xff0c;除非字符串就是 "0"&#xff0c;否则返回的字符串中不能含有前导零。 示例 1&#xff1a; 输入&#xff1a;n 2 输出&…

基于Springboot的数字化农家乐管理平台(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的数字化农家乐管理平台&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系…

踏上R语言之旅:解锁数据世界的神秘密码(四)

文章目录 前言一、多元线性回归1.多元线性回归模型的建立2.多元线性回归模型的检验 二、多元线性相关分析1.矩阵相关分析2.复相关分析 三、回归变量的选择方法1.变量选择准则2.变量选择的常用准则3.逐步回归分析 总结 前言 回归分析研究的主要对象是客观事物变量间的统计关系。…

选择内核注意力 SK | Selective Kernel Networks

论文名称&#xff1a;《Selective Kernel Networks》 论文地址&#xff1a;https://arxiv.org/pdf/1903.06586.pdf 代码地址&#xff1a;https://github.com/implus/SKNet 在标准的卷积神经网络中&#xff0c;每层人工神经元的感受野被设计为具有相同的大小。神经科学界已经广…

使用OkHttp 缓存 API 调用提高Android应用性能

使用OkHttp 缓存 API 调用提高Android应用性能 坦率地说&#xff0c;我们都遇到过这样的情况——焦急地刷新应用&#xff0c;看着加载图标不停地旋转&#xff0c;等待那个至关重要的 API 响应。这样的等待我们已经是炉火纯青了&#xff0c;是吧&#xff1f;手指有节奏地轻敲屏…

《R语言与农业数据统计分析及建模》——多重共线性和逐步回归

一、多重共线性 多重共线性&#xff1a;在多元线性回归时&#xff0c;多个自变量之间存在高度相关关系&#xff0c;时模型估计失真或难以估计准确的情况。 一般地&#xff0c;多元线性回归中自变量间应尽量相互独立。常规模型诊断方法难以检测多重共线性。 1、案例解释 作物产…

ActiveMQ 反序列化漏洞 (CVE-2015-5254)

一、漏洞描述 Apache ActiveMQ 是由美国阿帕奇&#xff08;Apache&#xff09;软件基金会开发的开源消息中间件&#xff0c;支持 Java 消息服务、集群、Spring 框架等。属于消息队列组件(消息队列组件&#xff1a;分布式系统中的重要组件&#xff0c;主要解决应用耦合、异步消息…

数据库|TiDB-Server API的高效应用指南

一、API介绍 1.Status 显示TiDB 连接数、版本和git_hash 信息 tidb-server_ip:status_port/status { "connections": 0, "version": "5.7.25-TiDB-v6.1.1", "git_hash": "5263a0abda61f102122735049fd0dfadc7b7f822" } 2.St…

mysql-sql-练习题-4-标记

标记 连续登录2-7天用户建表排名找规律 最大连胜次数建表只输出连胜结果输出所有连续结果 连续登录2-7天用户 建表 create table continuous_login(user_id1 integer comment 用户id,date_login date comment 登陆日期 ) comment 用户登录表;insert into continuous_login val…

LT2611UX四端口 LVDS转 HDMI2.0,带音频

描述LT2611UX 是一款面向机顶盒、DVD 应用的高性能 LVDS 至 HDMI2.0 转换器。LVDS输入可配置为单端口、双端口或四端口&#xff0c;具有1个高速时钟通道和3~4个高速数据通道&#xff0c;工作速率最高为1.2Gbps/通道&#xff0c;可支持高达19.2Gbps的总带宽。LT2611UX 支持灵活的…

002 springCloudAlibaba Sentinel流控-关联

当与A关联的资源B达到阀值后&#xff0c;就限流A自己 文章目录 FlowLimitController.javaSentinelServerApplication.javaServletInitializer.javaapplication.yamlpom.xmlpom.xml 启动Sentinel8080 - java -jar sentinel-dashboard-1.7.0.jar 启动微服务8401 启动8401微服务…

72、栈-每日温度

思路&#xff1a; 第一种方法&#xff0c;双循环&#xff0c;第一层循环拿出一个元素&#xff0c;第二层循环寻找最近比当前大的元素位置。 第二种方法&#xff1a;使用栈来实现。 初始化&#xff1a; int[] ans 用来存储每一天之后多少天温度会升高。Stack<Integer> 用…

Java | AI+编程 | 如何使用通义灵码提升开发效率

大家好&#xff0c;我是程序员影子 | 全网同名 一名致力于帮助更多朋友快速入门编程的程序猿 今天&#xff0c;我将以小白入门的视角带着大家学会如何在Idea上使用通义灵码&#xff0c;提高开发效率&#xff0c;减少重复工作&#xff1b;话不多说&#xff0c;我们直接进入正题…

golang beego结合wire依赖注入及自动路由

1 安装wire 1.1 通过命令直接安装 go install github.com/google/wire/cmd/wirelatest 1.2 通过go get方式安装 go get github.com/google/wire/cmd/wire进入目录编译 cd C:\Users\leell\go\pkg\mod\github.com\google\wirev0.6.0\cmd\wire go build 然后将wire.exe移动到…

广交会烹饪机器人用上大模型 支付宝小程序云提供技术支持

近日&#xff0c;第135届广交会正在火热进行&#xff0c;记者获悉&#xff0c;支付宝小程序云助力合作伙伴田螺云厨&#xff0c;在烹饪机器人上开始用上大模型技术。各类智能产品的亮相&#xff0c;从中国制造迈向中国创造&#xff0c;也成为广交会的一个亮点。 &#xff08;图…

鲲鹏华为云--OBS

文章目录 1.创建桶2.上传对象3.下载对象4.分享对象5. 删除对象6.删除桶 1.创建桶 创建桶 2.上传对象 点击创建的桶–“上传对象” 拖拽本地文件或文件夹至“上传对象”区域框内添加待上传的文件。 也可以通过单击“上传对象”区域框内的“添加文件”&#xff0c;选择本地…