【JavaEE初阶】多线程(5 单例模式 \ 阻塞队列)

news/2024/12/22 11:25:39/

欢迎关注个人主页:逸狼


创造不易,可以点点赞吗~

如有错误,欢迎指出~



目录

实例1: 单例模式

饿汉模式

懒汉模式

实例2:阻塞队列

生产者消费者模型 

优点

​编辑 代价

简单实现一个生产者消费者模型 

Java标准库中的阻塞队列

​编辑 模拟实现一个阻塞队列


实例1: 单例模式

单例模式 是一种设计模式,(固定套路,针对一些特定的场景,给出的一些比较好的解决方案)

开发中,希望有的类在一个进程中,不应该存在多个实例(对象),此时就可以使用单例模式(单个实例/对象),限制某个类只能有唯一实例, 比如 一般来说,一个程序 只有一个数据库,对应的mysql服务器只有一份,此时DataSource这个类就没有必要创建出多个实例了,此时使用单例模式描述DataSource,避免不小心创建出多个实例

Java中单例模式的实现有很多种,下面介绍两种最主流的写法: 饿汉模式 和 懒汉模式

饿汉模式

程序启动,在类被加载的时候, 就会创建出这个单例的实例, 不涉及线程安全问题

// 创建一个单例的类
// 饿汉方式实现.
// 饿 的意思是 "迫切"
// 
class Singleton{private static Singleton instance =new Singleton();public static Singleton getInstance(){return instance;}//单例模式的 最关键部分private Singleton(){}
}

 

单例模式只能避免别人"失误",无法应对别人的"故意攻击"(可以通过 反射 和 序列化反序列化打破上述单例模式)

懒汉模式

推迟了创建实例的时机,在程序第一次使用这个实例的时候 才会创建实例 ,涉及线程安全问题

class SingletonLazy{//此处先把实例设为null,先不着急创建实例private static volatile SingletonLazy instance =null;private static Object locker =new Object();public static SingletonLazy getInstance(){if(instance==null){synchronized(locker){if(instance==null){instance = new SingletonLazy();}}}return instance;}private SingletonLazy(){ }
}

可能存在的线程安全问题

通过加锁解决,将if判定和new赋值操作,打包成原子操作  

 

双重if 判定 , 通过if条件判定是否要加锁 

编译器优化(指令重排序) 造成的线程安全问题

在执行3)步骤之后并且未执行2)步骤时 如果线程发生切换,此时会直接返回instance,但是这个被返回的对象是没有被初始化的,由于该对象未初始化,一旦调用该对象里的成员,都可能是错误的值,引起一系列不可预期的情况.

解决方法:在instace的前面加上volatile.

实例2:阻塞队列

阻塞队列 是在普通队列(先进先出)的基础上做出扩充:

  • 线程安全  (标准库中原有的队列Queue和其子类,默认都是线程不安全的)
  • 具有阻塞特性  
    • 如果队列为,进行出队列操作 就会出现阻塞,一直阻塞到其他线程往队列里添加元素为止
    • 如果队列为,进行入队列操作 也会出现阻塞,一直阻塞到其他线程从队列中取走元素为止

生产者消费者模型 

该模型是基于阻塞队列的最大应用场景

优点

使用该模型的优点:

1,有利于服务器之间的"解耦合"

 2,通过中间的阻塞队列,可以起到"削峰填谷"的效果(在遇到请求量激增的情况下,可以有效保护下游服务器,不会被请求冲垮)

通常谈到的"阻塞队列"是代码中的一个数据结构,但是由于这个东西太好用了,以至于被单独封装成了一个服务器程序,并且在单独的服务器机器上部署,此时这样的阻塞队列有了一个新的名字:"消息队列"(Message Queue ,MQ) 

 代价

  • 需要更多的机器 来部署这个消息队列
  • A和B之间的通信延时 会变长

简单实现一个生产者消费者模型 

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;public class Demo27 {public static void main(String[] args) {// BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(1000);MyBlockingQueue queue = new MyBlockingQueue(1000);// 生产者线程Thread t1 = new Thread(() -> {int i = 1;while (true) {try {queue.put("" + i);System.out.println("生产元素 " + i);i++;// 给生产操作, 加上 sleep, 生产慢点, 消费快点Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});// 消费者线程Thread t2 = new Thread(() -> {while (true) {try {Integer i = Integer.parseInt(queue.take());System.out.println("消费元素 " + i);// 给消费操作, 加上 sleep, 生产快点, 消费慢点// Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();t2.start();}
}

Java标准库中的阻塞队列

入队列产生阻塞效果,代码演示

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;public class Demo18 {public static void main(String[] args) throws InterruptedException {BlockingQueue<String> queue=new ArrayBlockingQueue<>(3);queue.put("111");System.out.println("put成功");queue.put("111");System.out.println("put成功");queue.put("111");System.out.println("put成功");queue.put("111");System.out.println("put成功");}
}

 模拟实现一个阻塞队列

 基于数组实现的一个阻塞队列


class MyBlockingQueue{private String[] data=null;private volatile int head =0;private volatile int tail =0;private volatile int size =0;public MyBlockingQueue(int capacity){data = new String[capacity];}public void put(String s) throws InterruptedException {synchronized (this){//下面有大量的修改操作,加上锁保证线程安全//可以单独定义一个锁对象,也可以使用this表示当前对象while(size == data.length){//队列满了
//                return;this.wait();}data[tail]=s;tail++;if(tail >= data.length){tail=0;//如果tail移到了末尾,直接让tail到0位置,构成循环队列}size++;this.notify();}}public String take() throws InterruptedException {String ret= "";synchronized (this){while(size == 0){
//                return null;this.wait();}ret = data[head];head++;if(head>=data.length){head = 0;}size--;this.notify();}return ret;}
}

注意:要将使用wait时的if判断换成while循环,使代码更加稳健

while 的作用,就是在wait被唤醒之后再次确认条件,看是否能继续执行


http://www.ppmy.cn/news/1527001.html

相关文章

双线性插值算法

线性插值 已知两个点(x1, y1)、(x2, y2),求它们中间坐标(x,y)。 2点求一条直线公式(双线性插值需要的基础公式),这里没有写成经典的AX+B的形式,因为这种形式从权重的角度更好理解。 经过简单整理成下面的格式: 则可以得到 y = a*y1 + (1-a)*y2 其中a和(1-a)为x距离x1和x…

09年408考研真题解析-计算机网络

[题34]在无噪声情况下&#xff0c;若某通信链路的带宽为3kHz&#xff0c;采用4个相位&#xff0c;每个相位具有4种振幅的QAM调制技术,则该通信链路的最大数据传输速率是&#xff08;B&#xff09; A.12 kbps B.24 kbps C.48 kbps D.96 kbps 解析&#xff…

【乐企】基础版本开票代码接口声明

本文是基础版全部代码,包括以下接口: 1、电子发票批量预赋码 2、查询授信额度 3、下载或退回授信额度 4、调整授信额度有效期 5、查询纳税人基本/风险信息 6、查询可用税率 7、查询税收分类编码信息 8、查询差额征税编码 9、数字化电子发票上传 10、查询数字化电子…

LLM - 理解 多模态大语言模型 (MLLM) 的指令微调与相关技术 (四)

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/142063880 免责声明&#xff1a;本文来源于个人知识与公开资料&#xff0c;仅用于学术交流&#xff0c;欢迎讨论&#xff0c;不支持转载。 完备(F…

SQL案例分析:计算延迟法定退休年龄

2024年9月13日第十四届全国人民代表大会常务委员会第十一次会议通过了《关于实施渐进式延迟法定退休年龄的决定》。 从2025年1月1日起&#xff0c;男职工和原法定退休年龄为五十五周岁的女职工&#xff0c;法定退休年龄每四个月延迟一个月&#xff0c;分别逐步延迟至六十三周岁…

系统架构设计师 大数据架构篇一

&#x1f310;大数据架构 大数据处理系统分析 &#x1f50d; 大数据处理系统三大挑战 &#x1f680; 非结构化数据处理&#xff1a;如何处理非结构化和半结构化数据。复杂性与不确定性&#xff1a;大数据复杂性、不确定性特征描述的刻画方法和大数据的系统建模。异构性影响&…

PHP环境搭建详细教程

PHP是一个流行的服务器端脚本语言&#xff0c;广泛用于Web开发。为了使PHP能够在本地或服务器上运行&#xff0c;我们需要搭建一个合适的PHP环境。本教程将结合最新资料&#xff0c;介绍在不同操作系统上搭建PHP开发环境的多种方法&#xff0c;包括Windows、macOS和Linux系统的…

828华为云征文|docker部署kafka及ui搭建

1.介绍 1.1 什么是华为云Flexus X实例 最近华为云828 B2B企业节正在举办&#xff0c;Flexus X实例的促销也非常给力&#xff0c;大家可以去看看。特别是对算力性能有要求&#xff0c;同时对自建MySQL、Redis、Nginx性能有要求的小伙伴&#xff0c;千万不要错过。Flexus云服务器…