【JavaEE】单例模式&阻塞队列

这篇具有很好参考价值的文章主要介绍了【JavaEE】单例模式&阻塞队列。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1.1 单例模式

啥是设计模式?

设计模式好比象棋中的 “棋谱”. 红方当头炮, 黑方马来跳. 针对红方的一些走法, 黑方应招的时候有一些固定的套路. 按照套路来走局势就不会吃亏.
软件开发中也有很多常见的 “问题场景”. 针对这些问题场景, 大佬们总结出了一些固定的套路. 按照这个套路来实现代码, 也不会吃亏.

单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例.
这一点在很多场景上都需要. 比如 JDBC 中的 DataSource 实例就只需要一个.
单例模式具体的实现方式, 分成 “饿汉” 和 “懒汉” 两种.

饿汉模式

类加载的同时, 创建实例.

public class SingletonHungry {
    //static 修饰成员变量,全局只有一个
    private static SingletonHungry instance = new SingletonHungry();
    //构造方法私有化,使类对象只有一个
     private SingletonHungry() {}
//    对外提供一个获取获取实例对象的方法
//    用static修饰方法
    public static SingletonHungry getInstance(){
        return instance;
    }
}

懒汉模式-单线程版

类加载的时候不创建实例. 第一次使用的时候才创建实例.

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

懒汉模式-多线程版

上面的懒汉模式的实现是线程不安全的.
线程安全问题发生在首次创建实例时. 如果在多个线程中同时调用 getInstance 方法, 就可能导致 创建出多个实例.
一旦实例已经创建好了, 后面再多线程环境调用 getInstance 就不再有线程安全问题了(不再修改 instance 了)
加上 synchronized 可以改善这里的线程安全问题.

public class SingletonLazy2 {
    private static SingletonLazy2 instance = null;
    private SingletonLazy2() {}

//    以下两种方法都可以
//    在获取成员变量时,先判断锁是否被占用
//
//    其实synchronized代码块只需要执行一次就够了,以现在的写法,只要调用了getInstance方法,都要竞争锁,锁竞争是非常耗费系统资源的
//    使用了synchronized就从用户态转到了内核态
    public static synchronized SingletonLazy2 getInstance() {
        if (instance == null) {
//            初始化过程只执行一次
                instance = new SingletonLazy2();
        }
        return instance;
    }

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

//    错误的!!!!!!!!!!!!
//    public static SingletonLazy2 getInstance() {
//        if (instance == null) {
//    此时已经判断instance为空,争抢锁之后就会创建一个新的实例对象
//            synchronized (SingletonLazy2.class){
//                instance = new SingletonLazy2();
//            }
//        }
//        return instance;
//    }

}

懒汉模式-多线程版(改进)

以下代码在加锁的基础上, 做出了进一步改动:

  • 使用双重 if 判定, 降低锁竞争的频率.
  • instance 加上了 volatile.
/**
 * 使用双重 if 判定, 降低锁竞争的频率.
 * 给 instance 加上了 volatile.
 *
 * 加锁 / 解锁是一件开销比较高的事情. 而懒汉模式的线程不安全只是发生在首次创建实例的时候.
 * 因此后续使用的时候, 不必再进行加锁了.
 * 外层的 if 就是判定下看当前是否已经把 instance 实例创建出来了.
 * 同时为了避免 "内存可见性" 导致读取的 instance 出现偏差, 于是补充上 volatile .
 * 当多线程首次调用 getInstance, 大家可能都发现 instance 为 null, 于是又继续往下执行来竞争锁,
 * 其中竞争成功的线程, 再完成创建实例的操作.
 * 当这个实例创建完了之后, 其他竞争到锁的线程就被里层 if 挡住了. 也就不会继续创建其他实例.
 */
//双重检查锁 DCL
public class SingletonDCL {

    //synchronized只能保证原子性和可见性,不能保证有序性(其他线程可能得到一个创建了对象(instance != null),但没有得到某些数据初始化的对象)
    //加上volatile保证有序性(可见性与有序性)
    private volatile static SingletonDCL instance = null;
    private SingletonDCL() {}
    public static  SingletonDCL getInstance() {

        //为了让后面的线程不再获取锁,避免锁竞争
        if (instance == null) {
            synchronized (SingletonDCL.class) {
                //完成初始化操作,只执行一次
                if (instance == null) {
                    instance = new SingletonDCL();
                }
            }
        }
        return instance;
    }
}

关于单例模式的饿汉和懒汉模式

  1. 工作中可以使用饿汉模式,因为书写简单且不易出现错
  2. 饿汉模式在程序加载时完成的初始化,但是由于计算机资源有限,为了节约资源,可以使用懒汉模式
  3. 懒汉模式就是在使用对象时再去完成初始化操作
  4. 懒汉模式在多线程模式可能出现线程安全问题
  5. 那么就需要使用synchronized包裹初始化代码块
  6. 初始化代码只执行一次,后序的线程在调用getInstance()时,依然会产生竞争锁,频繁进行用户态和内核态的切换,非常浪费所资源
  7. 这时候就是可以用double check lock(DCL)的方式,在外层加一个非空校验,避免无用的锁竞争
  8. synchronized只能保证原子性和可见性,不能保证有序性(其他线程可能得到一个创建了对象(instance != null),但没有得到某些数据初始化的对象),再使用volatile解决有序性问题
  9. 描述指令重排序可能出现的问题(使某些代码没有得到执行)

1.2 阻塞队列是什么

阻塞队列是一种特殊的队列. 也遵守 “先进先出” 的原则.
阻塞队列能是一种线程安全的数据结构, 并且具有以下特性:

  • 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.
  • 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.

阻塞队列的一个典型应用场景就是 “生产者消费者模型”. 这是一种非常典型的开发模型.

生产者消费者模型

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取.
1) 阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力.

比如在 “秒杀” 场景下, 服务器同一时刻可能会收到大量的支付请求. 如果直接处理这些支付请求, 服务器可能扛不住(每个支付请求的处理都需要比较复杂的流程). 这个时候就可以把这些请求都放 到一个阻塞队列中, 然后再由消费者线程慢慢的来处理每个支付请求.
这样做可以有效进行 “削峰”, 防止服务器被突然到来的一波请求直接冲垮.

2) 阻塞队列也能使生产者和消费者之间 解耦.

比如过年一家人一起包饺子. 一般都是有明确分工, 比如一个人负责擀饺子皮, 其他人负责包. 擀饺子皮的人就是 “生产者”, 包饺子的人就是 “消费者”.
擀饺子皮的人不关心包饺子的人是谁(能包就行, 无论是手工包, 借助工具, 还是机器包), 包饺子的人也不关心擀饺子皮的人是谁(有饺子皮就行, 无论是用擀面杖擀的, 还是拿罐头瓶擀, 还是直接从超市买的).

标准库中的阻塞队列

在 Java 标准库中内置了阻塞队列. 如果我们需要在一些程序中使用阻塞队列, 直接使用标准库中的即可.文章来源地址https://www.toymoban.com/news/detail-441511.html

  • BlockingQueue 是一个接口. 真正实现的类是 LinkedBlockingQueue.
  • put 方法用于阻塞式的入队列, take 用于阻塞式的出队列.
  • BlockingQueue 也有 offer, poll, peek 等方法, 但是这些方法不带有阻塞特性.
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
// 入队列
queue.put("abc");
// 出队列. 如果没有 put 直接 take, 就会阻塞. 
String elem = queue.take();

自己实现阻塞队列:

/**
 * 通过 "循环队列" 的方式来实现.
 * 使用 synchronized 进行加锁控制.
 * put 插入元素的时候, 判定如果队列满了, 就进行 wait. (注意, 要在循环中进行 wait. 被唤醒时不一
 * 定队列就不满了, 因为同时可能是唤醒了多个线程).
 * take 取出元素的时候, 判定如果队列为空, 就进行 wait. (也是循环 wait)
 */
public class MyBlockingQueue {
    private int[] elementData = new int[10];
    private int head;
    private int tail;
    private volatile int size;
    public void put(int val) throws InterruptedException {
        synchronized (this) {
            //判满
            // 此处最好使用 while.(可能会出现虚假唤醒)
             // 否则 notifyAll 的时候, 该线程从 wait 中被唤醒,
             // 但是紧接着并未抢占到锁. 当锁被抢占的时候, 可能又已经队列满了
             // 就只能继续等待
            while (size >= elementData.length) {
                wait();
            }
//        插入元素
            elementData[tail] = val;
            tail++;
            if (tail >= elementData.length) {
                tail = 0;
            }
            size++;
            this.notifyAll();
        }
    }
    public int take() throws InterruptedException {
        synchronized (this) {
            while (size <= 0) {
                wait();
            }
            int ret = elementData[head];
            head++;
            if (head >= elementData.length) {
                head = 0;
            }
            size--;
            //有空位就唤醒
            this.notifyAll();
            return ret;
        }
    }

}

生产者消费者模型

import java.util.concurrent.TimeUnit;

public class Demo03_ProducerConsumer {
    // 定义一个阻塞队列
    private static MyBlockingQueue queue = new MyBlockingQueue();

    public static void main(String[] args) {
        // 创建生产者线程
        Thread producer = new Thread(() -> {
            int num = 1;
            while (true) {
                // 生产一条打印一条日志
                System.out.println("生产了元素 " + num);
                try {
                    // 把消息放入阻塞队列中
                    queue.put(num);
                    num++;
                    // 10ms
                    TimeUnit.MILLISECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        // 启动生产者
        producer.start();

        // 创建消费者线程
        Thread consumer = new Thread(() -> {
            while (true) {
                try {
                    // 从队列中获取元素(消息)
                    int num = queue.take();
                    // 打印一下消费日志
                    System.out.println("消费了元素 :" + num);
                    // 休眠1秒
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        // 启动消费者
        consumer.start();
    }
}

到了这里,关于【JavaEE】单例模式&阻塞队列的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 【Java多线程】关于多线程的一些案例 —— 单例模式中的饿汉模式和懒汉模式以及阻塞队列

    【Java多线程】关于多线程的一些案例 —— 单例模式中的饿汉模式和懒汉模式以及阻塞队列

    目录 1、单例模式 1.1、饿汉模式 2.1、懒汉模式  2、阻塞队列 2.1、BlockingQueue 阻塞队列数据结构 对框架和设计模式的简单理解就是,这两者都是“大佬”设计出来的,让即使是一个代码写的不太好的“菜鸡程序员”也能写出还可以的代码。 设计模式也可以认为是对编程语言语

    2024年03月23日
    浏览(13)
  • 【javaEE】阻塞队列、定时器、线程池

    【javaEE】阻塞队列、定时器、线程池

    目录 🌴一、阻塞队列 1.概念 2.生产者消费者模型 3.阻塞队列的实现 🏹二、定时器 1.引出定时器 2.定时器的实现 🔥三、线程池 1.引出线程池 2.ThreadPoolExecutor 构造方法 3.标准数据库的4种拒绝策略【经典面试题】【重点掌握】 4.线程池的实现   1.概念 ✨对于队列,首先我们想

    2023年04月21日
    浏览(9)
  • 【JavaEE初阶】多线程(四)阻塞队列 定时器 线程池

    【JavaEE初阶】多线程(四)阻塞队列 定时器 线程池

    概念 阻塞队列是一种特殊的队列. 也遵守 “ 先进先出 ” 的原则. 阻塞队列能是一种线程安全的数据结构, 并且具有以下特性: 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素. 当队列空的时候, 继续出队列也会阻塞,直到有其他线程往队列中插入元素

    2023年04月26日
    浏览(13)
  • [JAVAee]单例模式

    目录 单例模式的含义 饿汉模式 注意事项 懒汉模式 单线程版本 多线程版本 多线程改进版本(推荐) 注意事项 解析这个名词: 单例中的单,指的是单个,一个的意思 而例,是实例的意思,实例也就是通常所说的对象. 合起来就是, 只能有一个实例对象的模式. 当然这是对于 某一个类

    2024年02月15日
    浏览(7)
  • JavaEE 初阶篇-深入了解单例模式(经典单例模式:饿汉模式、懒汉模式)

    JavaEE 初阶篇-深入了解单例模式(经典单例模式:饿汉模式、懒汉模式)

    🔥博客主页: 【 小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍ 文章目录         1.0 单例模式的概述         2.0 单例模式 - 饿汉式单例         2.1 关于饿汉式单例的线程安全问题         3.0 单例模式 - 懒汉式单例         3.1 关于懒汉式单例的线程安全问题      

    2024年04月15日
    浏览(20)
  • JavaEE:单例模式(饿汉模式和懒汉模式)精讲

    JavaEE:单例模式(饿汉模式和懒汉模式)精讲

    前言 什么是单例模式? 其实用通俗的话就是程序猿约定俗成的一些东西,就比如如果你继承了一个抽象类,你就要重写里面的抽象方法,如果你实现了一个接口,你就要重写里面的方法。如果不进行重写,那么编译器就会报错。这其实就是一个规范。 而单例模式能保证某个

    2024年02月04日
    浏览(10)
  • JavaEE(系列8) -- 多线程案例(单例模式)

    JavaEE(系列8) -- 多线程案例(单例模式)

    目录 1. 设计模式 2. 单例模式 -- 饿汉模式 3. 单例模式 -- 懒汉模式  4. 单例模式(懒汉模式-多线程)  什么是设计模式?         设计模式好比象棋中的 \\\"棋谱\\\". 红方当头炮, 黑方马来跳. 针对红方的一些走法, 黑方应招的时候有一些固定的套路. 按照套路来走局势就不会吃亏

    2024年02月05日
    浏览(10)
  • 【JavaEE初阶】多线程(三)volatile wait notify关键字 单例模式

    【JavaEE初阶】多线程(三)volatile wait notify关键字 单例模式

    摄影分享~~ 以上代码运行的结果可能是输入1后,t1这个线程并没有结束。而是一直在while中循环。而t2线程已经执行完了。 以上情况,就叫做 内存可见性问题 这里使用汇编来理解,大概分为两步操作: load,把内存中flag的值,读到寄存器中。 cmp,把寄存器中的值,和0进行比

    2023年04月25日
    浏览(12)
  • 什么是Java中的阻塞队列和非阻塞队列?

    首先,让我们从基础概念开始。在计算机科学中,数据结构可以分为两种:队列和管道。队列是一种先进先出(FIFO)的数据结构,你可以想象成排队买电影票的情况。你加入队伍的时候,你可以决定站在哪里,但是一旦决定站在哪里,你就不能改变位置。而一旦你到达队尾,

    2024年02月14日
    浏览(14)
  • 阻塞队列(消息队列)

    阻塞队列(消息队列)

    队列是一种先进先出的数据结构。而阻塞队列也是一种特殊的队列,也遵守”先进先出“的原则。 阻塞队列是一种线程安全的的数据结构,并且具有以下 特性 : 1、队列往进写元素是从队尾插入,队首取出 2、当插入元素的时候,先判断一下,队列是否已经满了,如果满了就

    2024年02月11日
    浏览(6)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包