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

这篇具有很好参考价值的文章主要介绍了线程安全版本的单例设计模式 与 生产者消费者模型简介。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

单例设计模式

单例设计模式——饿汉式

单例设计模式——懒汉式

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

生产者消费者模型

介绍

优点

补充:关于阻塞队列


单例设计模式

单例设计模式能够保证某个类的实例在程序运行过程中始终都只会存在一份。这一点在很多场景上都有需要,比如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方法,但这两个方法是不能实现阻塞效果的,因此我们通常使用前两个方法。

练习:自己实现一个数组版本的阻塞队列文章来源地址https://www.toymoban.com/news/detail-423703.html

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;
    }
}

到了这里,关于线程安全版本的单例设计模式 与 生产者消费者模型简介的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 设计模式3:单例模式:静态内部类模式是怎么保证单例且线程安全的?

    上篇文章:设计模式3:单例模式:静态内部类单例模式简单测试了静态内部类单例模式,确实只生成了一个实例。我们继续深入理解。 静态变量什么时候被初始化? 这行代码 private static Manager instance = new Manager(); 什么时候执行? 编译期间将.java文件转为.class文件,运行期间

    2024年02月12日
    浏览(45)
  • 【Java中23种设计模式-单例模式2--懒汉式2线程安全】

    加油,新时代打工人! 简单粗暴,学习Java设计模式。 23种设计模式定义介绍 Java中23种设计模式-单例模式 Java中23种设计模式-单例模式2–懒汉式线程不安全 通过运行结果看,两个线程的地址值是相同的,说明内存空间里,创建了一个对象。

    2024年02月20日
    浏览(43)
  • [Unity] 单例设计模式, 可供继承的单例组件模板类

    一个可供继承的单例组件模板类: 因为 Unity 是单线程的, 所以在这里没有必要使用双检索 例如你要创建一个全局的单例管理类, 可以这样使用: 尽量避免让 SingletonComponent 帮你创建组件, 因为它只是单纯的将组件创建, 并挂载到空对象上, 而不会进行任何其他行为. 如果你的组件

    2024年02月08日
    浏览(41)
  • 【Linux】线程池设计/单例模式/STL、智能指针与线程安全/读者写者问题

    线程池:一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可

    2024年02月03日
    浏览(44)
  • 设计模式(单例模式,工厂模式),线程池

    目录 什么是设计模式? 单例模式 饿汉模式 懒汉模式 工厂模式 线程池 线程池种类 ThreadPoolExcutor的构造方法: 手动实现一个线程池  计算机行业程序员水平层次不齐,为了 让所有人都能够写出规范的代码, 于是就有了设计模式, 针对一些典型的场景,给出一些典型的解决方案 单例

    2024年02月11日
    浏览(40)
  • 【Linux】简单线程池的设计与实现 -- 单例模式

    线程池: 一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而 线程池维护着多个线程,等待着监督管理者分配可并发执行的任务 。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。

    2024年02月12日
    浏览(43)
  • 【Java|多线程与高并发】设计模式-单例模式(饿汉式,懒汉式和静态内部类)

    设计模式是一种在软件开发中常用的解决复杂问题的方法论。它提供了一套经过验证的解决方案,用于解决特定类型问题的设计和实现。设计模式可以帮助开发人员提高代码的可重用性、可维护性和可扩展性。 设计模式有很多,本文主要介绍单例模式. 单例模式是一种创建型设

    2024年02月11日
    浏览(53)
  • linux线程池、基于线程池的单例模式、读者写者问题

    线程池: 一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。

    2024年02月03日
    浏览(36)
  • 实际开发中常用的设计模式--------单例模式(知识跟业务场景结合)-----小白也能看懂(通俗易懂版本)

    1.定义 单例模式是一种创建型设计模式,它通过使用私有构造函数和静态方法来确保一个类只有一个实例,并且提供全局访问点来获取该实例。 通过使用单例模式,我们可以方便地管理全局唯一的对象实例,并且避免了多次创建相同类型的对象所带来的资源浪费问题 2.业务场

    2024年02月12日
    浏览(42)
  • C++11并发与多线程笔记(7) 单例设计模式共享数据分析、解决,call_once

    程序灵活,维护起来可能方便,用设计模式理念写出来的代码很晦涩,但是别人接管、阅读代码都会很痛苦 老外应付特别大的项目时,把项目的开发经验、模块划分经验,总结整理成设计模式 中国零几年设计模式刚开始火时,总喜欢拿一个设计模式往上套,导致一个小小的

    2024年02月12日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包