多线程代码案例-阻塞队列

简介: 多线程代码案例-阻塞队列

目录

💚1.什么是阻塞队列

💚2.生产者消费者模型

💚3标准库实现阻塞队列

💚4.自己实现一个阻塞队列


1.阻塞队列


我们之前在数据结构已经学了队列,什么是队列,我们来回忆一下,队列,是一种数据结构,先进先出.


阻塞队列也如此,先进先出,但是相比队列,它带有阻塞功能,当队列为满,要阻塞等待,当队列为空的时候,要阻塞等待.


因此,阻塞队列是线程安全的数据结构


当队列满的时候,会进入阻塞等待,直到有线程从队列取出元素


当队列空的时候,会进入阻塞等待,直到有线程在队列添加元素


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

需要我们重点掌握

2.生产者消费者模型

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题

举个简单的例子

包饺子的问题

一家四口人要包饺子

如果每个人擀一个皮,再包一个,这样就太低效了,如何包是最高效率的呢?

爸爸和大女儿作为生产者,(此处是生产饺子皮),包饺子皮,包好放到一个案板上,妈妈和小女儿一起包(此时是消费饺子皮)


7754e41fda6242e3bf6332cf30636622.png

此时这个案板就是相当于一个阻塞队列

当饺子皮放满了,妈妈和小女儿就会告诉爸爸和大女儿先别擀面皮了,那么生产者就可以阻塞等待一会,当案板皮没了,爸爸和大女儿告诉消费者阻塞等待一会


那么这样做提高了线程执行效率


下面来说一说阻塞队列的好处


1.让代码块之间解耦合


啥是耦合呢,就是代码块和代码块之间的关联性的强弱

举个例子,当自己的好朋友生病了,作为好友,我需要去探望,那么如果是不相关的ABCD,那么我可以直接不管,我和我好友的耦合性就很高,相反,我和ABCD耦合性很低


说到这里,顺便说一下啥是内聚


内聚就是功能一样的代码放在一起,再举个例子,衣服要分门别类的放,就是相同的一类的要放在一起


我们写代码,要遵守"高内聚,低耦合"


我们再来举一个计算机的例子


094c88ef48af487ea7906aae7bf7782d.png

A服务器给B服务器发送请求,B服务器给A响应


服务器处理请求的时候,很耗费硬件资源,包括不限于(CPU,内存,硬盘,带宽)......


所以当某个硬件资源达到瓶颈,服务器就有挂的风险


假设请求量非常多,那么B回应不过来,B服务器很有可能就挂掉了,它俩耦合性就很高,那么A也就挂掉了


所以我们可以采用增加一个阻塞队列的方式


441ecab428a540fe921a95eba4085700.png

这样增加一个阻塞队列,降低了耦合性,B挂,不影响A


2.削峰填谷

1.削峰


e045ea5e87d24b6793d5b691e16c9116.png

还用这个例子,当A的请求很多的时候,会影响B的响应速率吗,答案是不会!

阻塞队列帮A承担了很多请求,让B保证平稳的速率响应请求,这就是削峰的意思


2.填谷:


当A需求猛增以后,迅速进入猛减期,此时会影响B响应速率吗,还是不会!

阻塞队列会自动调节,当 请求量突然骤减,阻塞队列会拿出之前积压的请求分配给B,这就是填谷


说完作用,差不多介绍完了,现在来看一看实现吧


3.标准库实现阻塞队列

采用BlockingQueue

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
//生产者消费者模型
public class ThreadDemo3 {
    public static void main(String[] args) {
        BlockingQueue<Integer> queue=new LinkedBlockingQueue();
        //消费者
        Thread t1=new Thread(()->{
            while(true){
                try {
                    int value=queue.take();
                    System.out.println("消费"+value);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
            t1.start();
            //生产者
        Thread t2=new Thread(()->{
            int value=0;
            while(true){
                try {
                    queue.put(value);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("生产"+value);
                value++;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t2.start();
    }
}


1af837d6802846b98cbb85a321905fea.png

标准库实现比较简单,我们自己实现就有难度了


4.自己实现阻塞队列


需要3步

1.实现一个普通队列

2.加上线程安全(加锁)

3.增加阻塞等待功能

废话不多说,上代码


//自己实现阻塞队列
    class  MyBlockingQueue{
     volatile    private int[] items=new int[1000];
    volatile     private int head=0;
     volatile    private int tail=0;
      volatile   private int size=0;
        //入队列
 synchronized    public void put(int elem) throws InterruptedException {
        if(size==items.length){
            //return;
            this.wait();
        }
        items[tail]=elem;
        tail++;
        if(tail==items.length){
            tail=0;
        }
        size++;
        this.notify();
    }
  synchronized   public Integer take() throws InterruptedException {
        if(size==0){
          //  return null;
            this.wait();
        }
        int value=items[head];
        head++;
        if(head==items.length){
            head=0;
        }
        size--;
        this.notify();
        return value;
    }
}
public class ThreadDemo2 {
    public static void main(String[] args) {
        MyBlockingQueue queue = new MyBlockingQueue();
        //消费者
        Thread t1 = new Thread(()->{
            while (true) {
                int value = 0;
                try {
                    value = queue.take();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("消费" + value);
            }
        });
        t1.start();
        Thread t2=new Thread(()->{
            int value=0;
            while(true){
                try {
                    queue.put(value);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("生产"+value);
                value++;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t2.start();
        System.out.println("hello");
    }
    }

6318f0ec948b45edaede17a847531f6b.png

解释:


当队列满就阻塞等待,取出元素再唤醒,继续执行增加操作(如蓝色线)


当队列空就阻塞等待,放进元素再唤醒,继续执行取出操作(如红色线)


在put和take方法上加上锁,保证原子性,线程安全


在这个操作中涉及到多次读以及修改操作,为了保证读取到的数据是正确的,也就会为了保证内存可见性,我们采用增加关键字volatile的操作


并且上述不可能同时wait,不可能有一个队列又空又满!!!


839bfaf4a48140d1ba5d0ce36824ef94.png


这里还有最后一个小问题

为啥要用while,不用if,?

看看wait的源码


wait可能会在线程执行过程中被提前唤醒,条件没成熟就醒了,这不符合代码逻辑,所以我们把if改为while,wait操作之前,发现条件不满足wait,等到wait被唤醒以后,再次判断wait唤醒是不是因为满足条件,不满足,就继续等待!!!


以上就是今天的所有内容了,我们下期再见啦!!!


7317382bf6874f668db994c998cfbd3e.png

相关文章
|
5天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
22 3
|
1月前
|
供应链 安全 NoSQL
PHP 互斥锁:如何确保代码的线程安全?
在多线程和高并发环境中,确保代码段互斥执行至关重要。本文介绍了 PHP 互斥锁库 `wise-locksmith`,它提供多种锁机制(如文件锁、分布式锁等),有效解决线程安全问题,特别适用于电商平台库存管理等场景。通过 Composer 安装后,开发者可以利用该库确保在高并发下数据的一致性和安全性。
39 6
|
2月前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
25 1
|
3月前
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
|
2月前
|
消息中间件 NoSQL 关系型数据库
【多线程-从零开始-捌】阻塞队列,消费者生产者模型
【多线程-从零开始-捌】阻塞队列,消费者生产者模型
34 0
|
3月前
|
安全 Java 调度
python3多线程实战(python3经典编程案例)
该文章提供了Python3中多线程的应用实例,展示了如何利用Python的threading模块来创建和管理线程,以实现并发执行任务。
70 0
|
4月前
|
开发者 C# 存储
WPF开发者必读:资源字典应用秘籍,轻松实现样式与模板共享,让你的WPF应用更上一层楼!
【8月更文挑战第31天】在WPF开发中,资源字典是一种强大的工具,用于共享样式、模板、图像等资源,提高了应用的可维护性和可扩展性。本文介绍了资源字典的基础知识、创建方法及最佳实践,并通过示例展示了如何在项目中有效利用资源字典,实现资源的重用和动态绑定。
121 0
|
4月前
|
Java 开发者
解锁Java并发编程的秘密武器!揭秘AQS,让你的代码从此告别‘锁’事烦恼,多线程同步不再是梦!
【8月更文挑战第25天】AbstractQueuedSynchronizer(AQS)是Java并发包中的核心组件,作为多种同步工具类(如ReentrantLock和CountDownLatch等)的基础。AQS通过维护一个表示同步状态的`state`变量和一个FIFO线程等待队列,提供了一种高效灵活的同步机制。它支持独占式和共享式两种资源访问模式。内部使用CLH锁队列管理等待线程,当线程尝试获取已持有的锁时,会被放入队列并阻塞,直至锁被释放。AQS的巧妙设计极大地丰富了Java并发编程的能力。
51 0
|
5天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
21 1
|
2月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
60 1