您的位置:首页 > 财经 > 产业 > 建设工程造价信息网官网_开发app流程_自己怎么做网页推广_sem推广计划

建设工程造价信息网官网_开发app流程_自己怎么做网页推广_sem推广计划

2025/5/14 9:49:00 来源:https://blog.csdn.net/2301_81073317/article/details/146372227  浏览:    关键词:建设工程造价信息网官网_开发app流程_自己怎么做网页推广_sem推广计划
建设工程造价信息网官网_开发app流程_自己怎么做网页推广_sem推广计划


目录

1.前言

2.正文

2.1阻塞队列引入

2.2标准库中的阻塞队列

2.3手搓阻塞队列

3.小结


1.前言

哈喽大家好吖,今天来给大家分享多线程学习中的阻塞队列,阻塞队列在我们开发中也是一个非常重要的内容,那么话不多说让我们开始吧。

2.正文

2.1阻塞队列引入

Java中的阻塞队列(Blocking Queue)是一种支持线程安全的队列,主要用于多线程编程中实现生产者-消费者模式。其核心特性是:当队列为空时,消费者线程会被阻塞,直到队列中有新元素;当队列满时,生产者线程会被阻塞,直到队列有空闲位置。这种机制能有效协调生产者和消费者的速度差异。

那我们为什么引入阻塞队列呢。阻塞队列一个应用场景,就是实现生产者消费者模型,在多线程编程中,这是一个典型的编码技巧。接下来讲解我们为什么要引入阻塞队列。


假设我们有这样一个场景:

两台服务器之间互相通信,如果是二者直接进行请求响应的话,二者本身代码肯定存储了部分对方的信息方便通信,这样耦合程度较高。

但如果过我们引入阻塞队列:

这样就是本来是A 和 B耦合,现在成了A和队列耦合,B 和队列耦合,降低耦合, 是为了让后续修改的时候,成本低。

在实际开放中,中间的这个阻塞队列甚至会被单独部署成一个服务(其中包含多个独立的阻塞队列,被称为消息队列)。

所以引入阻塞队列的一个原因就是“降低耦合程度”


引入阻塞队列还有另一个原因,在引入一个场景:

A 这边遇到一波流量激增,此时每个请求都会转发给 B,B也会承担一样的压力~。很容易就把 B给搞挂了。
一般来说,A这种上游的服务器,尤其是入口的服务器,干的活更简单,单个请求消耗的资源数少。
像B这种下游的服务器,通常承担更重的任务量,复杂的计算/存储工作,单个请求消耗的资源数更多。
日常工作中,确实是会给B这样角色的服务器分配更好的机器,即使如此,也很难保证B承担的访问量能够比A更高。

这个时候我们在AB两者中添加一个阻塞队列:

队列服务器,针对单个请求,做的事情少,往往就比较耐造,a一旦激增,b可以不关心队列中的数据量有多少,可以慢慢的处理阻塞队列的请求数据,起到了“削峰填谷”的作用。


但显然,这样操作也会付出一定的代价:

  •  引入队列之后,整体结构会变得复杂,此时部署生产环境,管理起来会更麻烦
  • 效率也会受到影响(毕竟有可能出现阻塞)

2.2标准库中的阻塞队列

官网讲解:BlockingQueue (Java SE 17 & JDK 17)

以下代码通过 阻塞队列 (ArrayBlockingQueue) 实现了一个典型的生产者-消费者模型,展示了多线程间如何安全协作。其核心逻辑可分为以下部分:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;public class demo4 {public static void main(String[] args) {BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);Thread t1 = new Thread(()->{int n = 0;while(true){try {queue.put(n);System.out.println("生产者" + n);n++;Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}});Thread t2 = new Thread(()->{while(true){try {int x = queue.take();System.out.println("消费者" + x);Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}});t1.start();t2.start();}
}

1. 阻塞队列的初始化

  • 作用:创建容量为10的阻塞队列,用于在生产者和消费者线程间传递数据。

  • 特性

    • 当队列满时,put() 操作会阻塞生产者线程。

    • 当队列空时,take() 操作会阻塞消费者线程。

    • 线程安全,无需额外同步。


2. 生产者线程逻辑

  • 行为

    1. 无限循环生成递增整数 n

    2. 调用 put(n) 将数据存入队列。若队列已满,生产者线程自动阻塞。

    3. 每次生产后睡眠1毫秒,模拟实际生产场景中的处理延迟。


3. 消费者线程逻辑

  • 行为

    1. 无限循环从队列中取出数据。

    2. 调用 take() 获取数据。若队列为空,消费者线程自动阻塞。

    3. 每次消费后睡眠1毫秒,模拟实际消费场景中的处理延迟。


4. 线程启动与协作

  • 作用:启动生产者和消费者线程,二者并行执行。

  • 协作机制

    • 生产者通过 put() 填充队列,消费者通过 take() 清空队列。

    • 阻塞队列自动协调两者的速度差异:生产者过快时队列满导致阻塞,消费者过快时队列空导致阻塞。

运行截图:

2.3手搓阻塞队列

为了更好地理解阻塞队列,我们可以不利用标准库自带的阻塞队列,我们手搓一个阻塞队列,这样可加深我们对阻塞队列的理解。

class MyBlockQueue{private String[] data = null;private int head = 0;private int tail = 0;private int size = 0;public MyBlockQueue(int number){data = new String[number];}public void put(String elem) throws InterruptedException{synchronized (this){while(size >= data.length){this.wait();}data[tail] = elem;tail++;if(tail >= data.length){tail = 0;}size++;this.notify();}}public String take() throws InterruptedException{synchronized (this) {while (size == 0) {this.wait();}String ret = data[head];head++;if (head >= data.length) {head = 0;}size--;this.notify();return ret;}}
}public class demo5 {public static int n = 0;public static void main(String[] args) {MyBlockQueue myBlockQueue = new MyBlockQueue(10);Thread t1 = new Thread(()->{while(true){try {myBlockQueue.put(n + "");System.out.println("生产元素" + n);n++;Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});Thread t2 = new Thread(()->{while(true) {try {String str = myBlockQueue.take();System.out.println("消费元素" + str);Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t1.start();t2.start();}
}

核心思路分析:


1. 数据结构与初始化

  • 环形数组:使用定长数组 data 存储元素,通过 head 和 tail 指针实现环形队列,避免频繁扩容。

  • 状态变量:size 记录当前队列元素数量,用于判断队列空/满。

  • 初始化:构造函数指定队列容量(new MyBlockQueue(10)),创建固定大小的数组。


2. 线程安全与同步机制

  • synchronized 锁:put 和 take 方法通过 synchronized(this) 实现互斥访问,确保同一时间只有一个线程操作队列。

  • 条件等待与唤醒:

    • 生产者等待条件:队列满时(size >= data.length),调用 wait() 阻塞生产者。

    • 消费者等待条件:队列空时(size == 0),调用 wait() 阻塞消费者。

    • 唤醒机制:每次插入/取出元素后调用 notify(),唤醒一个等待线程(可能是生产者或消费者)。


3. put() 方法详解

  • 阻塞逻辑:

    • 使用 while 而非 if 避免虚假唤醒(如线程被意外唤醒但条件未满足)。

  • 添加元素:

    • 元素放入 tail 位置,tail 指针循环后移(tail = (tail + 1) % data.length 的等价实现)。

    • size++ 更新队列元素数量。

  • 唤醒消费者:notify() 通知可能阻塞的消费者线程。


4. take() 方法详解

  • 阻塞逻辑:

    while (size == 0) { this.wait(); } // 队列空时等待

  • 取出元素:

    • 从 head 位置取元素,head 指针循环后移。

    • size-- 更新队列元素数量。

  • 唤醒生产者:notify() 通知可能阻塞的生产者线程。


5. 生产者-消费者模型演示

  • 生产者线程 (t1):

    • 不断生成元素(n++),调用 put() 插入队列。

    • 每次生产后休眠 1 秒,模拟生产耗时。

  • 消费者线程 (t2):

    • 不断调用 take() 取出元素并打印。

    • 每次消费后休眠 1 秒,模拟处理耗时。

  • 运行效果:

    • 生产和消费速率相同(1秒/次),队列不会满或空。

    • 若生产快于消费,队列满时生产者阻塞;反之消费者阻塞。

运行截图:

3.小结

今天的分享到这里就结束了,喜欢的小伙伴点点赞点点关注,你的支持就是对我最大的鼓励,大家加油!

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com