线程安全版本的单例设计模式 与 生产者消费者模型简介

news2024/12/29 11:16:40

目录

单例设计模式

单例设计模式——饿汉式

单例设计模式——懒汉式

单例设计模式——懒汉式(优化步骤)

生产者消费者模型

介绍

优点

补充:关于阻塞队列


单例设计模式

单例设计模式能够保证某个类的实例在程序运行过程中始终都只会存在一份。这一点在很多场景上都有需要,比如JDBC中DataSource的实例就只需要一个就可以了。为了满足这种需求,程序开发中的大佬们设计出了单例模式的一种方式供我们使用。单例模式的版本有饿汉式和懒汉式两种,接下来一块看一下这两种设计模式的使用。

单例设计模式——饿汉式

这里的‘饿’可以理解为着急的意思,在程序中的体现为:“在类加载的时候就进行了实例对象的创建”,同时为了保证外部能够获得这个类的实例并且只能获得同一个实例对象,遂将该类的构造方法进行了私有化,然后对外提供了获得该唯一实例的方法。单例设计模式——“饿汉式”程序设计如下:

/**
* 单例设计模式——饿汉式
*/
public class SingleTonHungry {
    private static SingleTonHungry instance = new SingleTonHungry();
    
    //对构造方法进行私有化,不让外部创建该类的实例对象
    private SingleTonHungry() {}

    //对外提供获得该类在类中的成员的唯一实例对象的方法
    public static SingleTonHungry getInstance() {
        return instance;
    }
}

关于线程安全与否:

单例设计模式——‘饿汉式’由于在类加载时就指定了类实例属性的值,在后续的过程中不再创建新的类实例,而只是通过该类提供的的类方法获得这个类实例对象,因此在多线程环境中,相当于说是对共享资源进行只读操作,因此饿汉式的单例设计模式是线程安全的。

单例设计模式——懒汉式

与“饿汉式”版本类似,为了保证只能有一份实例,它也对自己的构造方法进行了私有化封装并向外提供了获取类属性实例的方法。这里‘懒’字的含义并不是贬义词,在程序中是指当使用到这个类的实例时,我才进行它的实例对象的创建,并且只创建一份。在还没有使用到这个类的实例时,不进行实例对象的创建。“懒汉式”的设计模式相对于“饿汉式”有诸多的优势并且在开发中通常更多的进行“懒汉式”单例设计模式的使用。它的程序设计如下:

/**
* 单例设计模式——懒汉式
*/
publiic class SingleTonIdler {
    private static volatile SingleTonIdler instance;
    
    //构造方法私有化,防止外部new对象
    private SingleTonIdler() {}

    //向外提供获得该类的单一实例的方法
    public static SingleTonIdler getInstance() {
        if(instance == null) {
            synchronized(SingleTonIdler.class) {
                if(instance == null) {
                    instance = new SingleTonIdler();
                }
            }
        }
        return instance;
    }
}

关于线程安全与否:

应为在高并发的环境下存在对共享资源的修改操作,因此懒汉式版本的单例设计模式不是线程安全的。

单例设计模式——懒汉式(优化步骤)

优化成线程安全版本:

😄因为对象实例的创建除主要发生在对外提供的获取该类实例对象的方法中,因此我们主要考虑在这个方法中对实例对象的创建只进行一次,并且只有一个线程来创建一次。

😄最简单的方法就是直接对获取实例的方法使用synchronized进行加锁操作,确保每次都有且只有一个线程进入该方法执行。但是我们想一下:实例对象的在线程安全的环境下创建一次后此后仅被进行读操作,就不再进行修改了,那么在实例对象后创建后如果继续对方法加锁,那么势必会降低程序的运行效率,造成严重的线程阻塞。

😄因此,我们可以先让所有线程进行该类实例对象是否创建的判断,如果创建了,就直接让这些并发线程带走已经创建好的实例对象,如果没有创建,就对创建过程加锁,让这些线程竞争该锁,拿到锁的线程进行一次单一实例对象的创建即可。这样,单一实例对象创建后,并发线程就不会再去争夺锁资源造成阻塞而造成程序运行效率的损失,而且也可以实现安全的并发访问操作。于是我们对单线程环境下的懒汉版本的单例设计模式程序进行了下面的改造。

  1. 在锁的外层先加一层实例对象是否已创建的判断,如果没有创建,则进入判断条件满足的语句块,否则直接返回已经创建了的实例对象
  2. 进入语句块后,使用synchronized对创建实例的过程进行加锁,某个线程竞争得到锁后,再进行一次实例对象否否已创建的判断,如果实例还没有创建,就创建,否则不满足判断条件,释放锁。这样来确保即使第一次并发有多个线程进入了判断体语句块,实例对象也只会被创建一次。
  3. 使用volatile修饰实例成员对象的引用,确保其表现出内存可见性。因为编译器可能一开始在判断到有多次对实例对象的访问的情况下会将该实例对象的值放在寄存器中,以至于其他线程对实例对象的引用的修改不能及时被另外的线程感知到,就会出现误判的结果。

生产者消费者模型

介绍

😄生产者和消费者模式就是通过一个容器来解决“生产者”和“消费者”之间高度耦合的情况。即“生产者”和“消费者”之间不是直接通信的,而是通过阻塞队列。

😄这样,“生产者”生产出物品后将物品扔给某条通道而不用关心“消费者”具体是谁;同理,“消费者”从这条通道中获得物品,也不必关系生产者究竟是谁。这就实现了生产者和消费者之间的解耦合。

优点

  1. 能够让多个服务器之间充分的解耦合
    😄例如,有两台服务器用户处理用户的请求,其中一台服务器用来接收用户的请求,另一台服务器用来具体处理用户的请求。接受请求的服务器和处理请求的服务器之间不必关心两者之间具体是怎么通信的,接受请求的服务器只需要将数据写入阻塞队列而不必关系数据是怎么进行处理的;同理,处理请求的服务器只需要从阻塞队列获取用户的请求并将用户的请求进行处理即可。处理请求的服务器返回处理结果的过程也类似,只需要将处理结果写入阻塞队列,接收用户请求的服务器从阻塞队列获得结果后返回即可。

     
  2. 能够对于用户请求进行“削峰填谷”
    😄未使用生产者消费者模型时,如果请求梁突然暴增将导致处理用户请求服务器的压力暴增。对于接收用户请求的服务器来说,计算量较小,用户请求量暴增并不可怕;但对于处理用户请求的服务器来说,计算量较大,当请求暴增时,压力骤增,很有可能程序就挂掉了。
    😄而对于使用了生产者消费者模型的结构来说,接收用户请求的服务器将请求扔到阻塞队列中,处理请求的服务器从阻塞队列中获取请求进行处理。当某个时间段用户的请求骤增时,阻塞队列能够起到缓冲用户请求的效果,使得处理请求的服务器仍保持自己的节奏从阻塞队列中获取请求,有条不紊的进行处理,而不用担心处理请求的服务器因请求暴增而带来的性能消耗问题,这就起到了上面我们说的“削峰”的效果。同理,当用户请求量降低时,阻塞队列继续工作,将阻塞在接受用户请求的服务器中的用户请求仍然有条不紊的输送往处理请求的服务器,这就实现了我们上边说的“填谷”。这个这就好比我们的三峡大坝(接受用户请求的服务器),在水量(用户请求)骤增时蓄水,在水量(用户请求)减少时进行开闸放水,为下游正常生活(处理用户请求的服务器)提供了保障。

补充:关于阻塞队列

简介

😄与普通队列相比,阻塞队列增加了以下的特性:

  • 是线程安全的
  • 当队列容量满的时候,继续入队就会阻塞,直到其他线程从队列中取走元素后阻塞恢复
  • 当队列容量为空时,继续出队也会产生阻塞,直到其他线程往队列中写入数据时,阻塞恢复

Java标准库中内置的阻塞队列

Java标准库中内置的阻塞队列时BlockingQueue<T>,这是一个接口,LinkedBlockingQueue<T>和ArrayBlockingQueue<T>实现了这个接口。

 我们可以直接使用Java中内置的阻塞队列:

  • 其中的put方法用于向阻塞队列中添加数据;take方法用于从阻塞队列中取出数据。
  • 当然因为它也实现了Queue接口也有普通队列的offer/poll方法,但这两个方法是不能实现阻塞效果的,因此我们通常使用前两个方法。

练习:自己实现一个数组版本的阻塞队列

public class MyBlockingQueue {
    private final Object locker;
    private final int[] elem;
    private int head;
    private int tail;
    private int size;
    public MyBlockingQueue() {
        locker = new Object();
        elem = new int[1000];   //初始化队列默认大小为1000
    }
    //入队列
    public void put(int value) throws InterruptedException {
        synchronized (locker) {
            if(size == elem.length) {
                //已满,阻塞.
                locker.wait();
            }
            elem[tail++] = value;
            if(tail >= elem.length) {
                tail = 0;
            }
            size++;
            //入队列成功,唤醒出队列的阻塞
            locker.notify();
        }
    }

    //出队列
    public int take() throws InterruptedException {
        synchronized (locker) {
            if(size == 0) {
                //队列为空,阻塞
                locker.wait();
            }
            size--;
            //元素出队列成功,唤醒入队列的阻塞队列
            //如果有多条线程阻塞,这里的唤醒是随机的
            locker.notify();
            return elem[head++];
        }
    }

    public int getSize() {
        return this.size;
    }
}

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

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

相关文章

代码随想录Day57

1143.最长公共子序列 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变字符的相对顺序的情况下删除某些字…

Cesium 实战-最新版(1.104.0)通过异步方式初始化地球,加载影像以及高程图层

Cesium 实战-最新版&#xff08;1.104.0&#xff09;通过异步方式初始化地球&#xff0c;加载影像以及高程图层 遇到问题初始化底图初始化高程&#xff08;监听载入完成事件&#xff0c;开启关闭高程&#xff09;初始化 3dtile Cesium 最新版&#xff08;1.104.0&#xff09;变…

2021地理设计组二等奖:基于地理大数据的南昌中心城区空间功能感知与分区

一、设计背景及意义 随着经济快速发展&#xff0c;城市功能类型也越来越多&#xff0c;在空间上逐渐聚集和演化&#xff0c;形成了居住区&#xff0c;商业区等城市功能区&#xff0c;而这些功能区没有明显边界&#xff0c;确定困难&#xff0c;如使用传统人力调查方法费时费力…

PHP快速入门16-用curl发起POST和GET的请求

文章目录 前言curl介绍发送GET请求发送POST请求其他选项 总结 前言 本文已收录于PHP全栈系列专栏&#xff1a;PHP快速入门与实战 在Web开发中&#xff0c;经常需要与其他服务器进行数据交互。而现在&#xff0c;绝大多数的接口都是基于HTTP协议的&#xff0c;因此我们需要学会…

Spring MVC的功能

1. 连接功能 1.1几种注解 RequestMapping最常用的注解之一&#xff0c;作用是用来路由注册&#xff08;注册接口的路由映射&#xff09;&#xff0c;即可修饰类也能修饰方法&#xff0c;默认情况下的RequestMapping即可接收Get请求也可以接收Post请求。也可以通过设置method来…

数据通信基础 - 数字传输系统(T1、E1)

文章目录 1 概述2 载波标准&#xff08;E1、T1&#xff09;2.1 T12.2 E1 3 扩展3.1 网工软考真题 1 概述 2 载波标准&#xff08;E1、T1&#xff09; 名称速率 Mbps信道个数每个语音信道的数据速率使用国家T11.5442456Kb/s美国、日本E12.0483264Kb/s欧洲、中国 2.1 T1 语音信…

5g网络变压器的特点与优势分析

5g网络变压器的特点与优势分析 5G网络变压器相比于2.5G和3G网络变压器&#xff0c;具有以下的特点和优势&#xff1a; 更高的频率&#xff1a;5G网络变压器可以支持更高的频率&#xff0c;从而实现更高的数据传输速率和更低的延迟。 更小的尺寸&#xff1a;5G网络变压器采用了…

为什么越来越多的网站选择海外主机?探究原因!

主机已成为网站托管的常用方式&#xff0c;但近年来越来越多的网站选择海外主机。这是为什么呢?在本文中&#xff0c;我们将探究海外主机的优点&#xff0c;并解释为什么越来越多的网站选择它们。 一、海外主机的优点 1、成本更低 海外主机的成本比独立主机低&#xff0c;因为…

文件系统和日志分析

文件系统 文件是存储在硬盘上的&#xff0c;硬盘的最小存储单位叫做"扇区”(sector)每个扇区存储512字节。一般连续八个扇区组成一个"块"(block)&#xff0c;一个块是4K大小&#xff0c;是文件存取的最小单位。操作系统读取硬盘的时候&#xff0c;是一次性连续…

哪个牌子手持洗拖一机好?热门洗地机盘点

在家居清洁中&#xff0c;越来越多的家庭选择了通过智能清洁家电来完成地面的清洁工作&#xff0c;其中洗地机时最受大家青睐的清洁工具&#xff0c;它不仅可以提高我们的清洁效率&#xff0c;还可以减轻清洁时的劳动强度。不过&#xff0c;不同品牌之间的产品的差距也是大有不…

MobPush Android SDK 集成指南

开发工具&#xff1a;Android Studio 集成方式&#xff1a;Gradle在线集成 安卓版本支持&#xff1a;minSdkVersion 19 集成准备 注册账号 使用PushSDK之前&#xff0c;需要先在MobTech官网注册开发者账号&#xff0c;并获取MobTech提供的AppKey和AppSecret&#xff0c;详情可…

世界大学机械工程TOP10,国内大学哪家强?

就在前不久世界大学的排名已经发布&#xff0c;机械工程学科是工科类学科当中代表学科之一&#xff0c;相信很多小伙伴是非常想要了解的。那么&#xff0c;我给大家介绍一下2023年QS世界大学&#xff08;机械工程&#xff09;学科排名。 本次排名比较分析了包括世界93个地区的…

智能洗地机哪个牌子更好用?好用不贵的洗地机推荐

近年来&#xff0c;智能家居产品越来越多&#xff0c;从一开始的扫地机器人到吸尘器再到后来的蒸汽拖把再到现在的洗地机&#xff0c;这些智能化清洁工具&#xff0c;不仅为我们节省了清洁的时间还拥有很好的清洁效果。其中洗地机是近年来最受大家青睐的清洁工具&#xff0c;那…

【Vulnhub】之symfonos1

一、 部署方法 在官网上下载靶机ova环境&#xff1a;https://download.vulnhub.com/symfonos/symfonos1.7z使用VMware搭建靶机环境攻击机使用VMware上搭建的kali靶机和攻击机之间使用NAT模式&#xff0c;保证靶机和攻击机放置于同一网段中。 二、 靶机下载安装 靶机下载与安…

电阻的选型

记点、 NOTE:通用的元器件选型步骤&#xff1a; A&#xff1a;明晰元器件的关机参数 B&#xff1a;结合具体的应用确定跟该应用最直接关联的关键参数 1、电阻的关键参数 2、电阻在电路的作用&#xff1a; 主要是用来是用来稳定和调节电流和电压。可作为分流器和分压器。也可…

ubuntu22.04安装nvidia驱动

ubuntu22.04安装nvidia驱动 环境前言直通显卡ubuntu2204虚拟机配置禁用默认显卡驱动安装显卡驱动查看显卡状态参考文章 环境 ESXi-7.0U3l ubuntu22.04 前言 在第一次成功之后&#xff0c;重启了虚拟机&#xff0c;失败了很多次&#xff0c;重装了n次系统和驱动&#xff0c;但…

452. 用最少数量的箭引爆气球

有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points &#xff0c;其中points[i] [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。 一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一…

DAX:概述ALL函数

简单的说&#xff0c;当ALL用作表函数时&#xff0c;忽略应用到表上的任何过滤器&#xff0c;并返回数据表&#xff1b;当ALL用作CALCULATE和CALCULATETABLE函数中修饰器时&#xff0c;ALL函数从扩展表中移除已经应用的过滤上下文。 注意自动存在(auto-eixist)对ALL()函数的影响…

前后端目前进展

进展 前端第一个vue2第二个vue2&#xff08;用来复盘结果报错&#xff09;第三个vue2 后端第一个django&#xff08;本地&#xff09;第二个django&#xff08;GPU&#xff09; 前后端连接 前端 (前端创建方式/流程详细见我的博客vue2创建) 第一个vue2 项目名&#xff1a;te…

戴尔G3 Ubuntu18.04双系统安装

ROS学习需要使用Linux系统&#xff0c;首先就是Ubuntu&#xff0c;我选择的是18.04.6这个版本&#xff0c;因为后面我要使用以Jetson Nano为主控的Jetbot进行ROS编程&#xff0c;Jetbot所带的出厂镜像就是18.04&#xff0c;为了方便程序移植&#xff0c;以及减少不必要的麻烦。…