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

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

目录

💚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面试第三天的笔记,讨论了线程安全、Thread与Runnable的区别、守护线程、ThreadLocal原理及内存泄漏问题、并发并行串行的概念、并发三大特性、线程池的使用原因和解释、线程池处理流程,以及线程池中阻塞队列的作用和设计考虑。
|
1月前
|
安全 Python
告别低效编程!Python线程与进程并发技术详解,让你的代码飞起来!
【7月更文挑战第9天】Python并发编程提升效率:**理解并发与并行,线程借助`threading`模块处理IO密集型任务,受限于GIL;进程用`multiprocessing`实现并行,绕过GIL限制。示例展示线程和进程创建及同步。选择合适模型,注意线程安全,利用多核,优化性能,实现高效并发编程。
38 3
|
9天前
|
消息中间件 安全 Kafka
"深入实践Kafka多线程Consumer:案例分析、实现方式、优缺点及高效数据处理策略"
【8月更文挑战第10天】Apache Kafka是一款高性能的分布式流处理平台,以高吞吐量和可扩展性著称。为提升数据处理效率,常采用多线程消费Kafka数据。本文通过电商订单系统的案例,探讨了多线程Consumer的实现方法及其利弊,并提供示例代码。案例展示了如何通过并行处理加快订单数据的处理速度,确保数据正确性和顺序性的同时最大化资源利用。多线程Consumer有两种主要模式:每线程一个实例和单实例多worker线程。前者简单易行但资源消耗较大;后者虽能解耦消息获取与处理,却增加了系统复杂度。通过合理设计,多线程Consumer能够有效支持高并发数据处理需求。
23 4
|
2月前
|
Java
【代码诗人】Java线程的生与死:一首关于生命周期的赞歌!
【6月更文挑战第19天】Java线程生命周期,如诗般描绘了从新建到死亡的旅程:创建后待命,`start()`使其就绪,获得CPU则运行,等待资源则阻塞,任务完或中断即死亡。理解生命周期,善用锁、线程池,优雅处理异常,确保程序高效稳定。线程管理,既是艺术,也是技术。
22 3
|
2月前
|
Java
【代码诗人】Java线程的生与死:一首关于生命周期的赞歌!
【6月更文挑战第19天】在Java中,线程经历新建、就绪、运行、阻塞和死亡5个阶段。通过`start()`从新建转为就绪,进而可能运行;阻塞可能因等待资源;完成任务或中断后死亡。管理线程生命周期涉及合理使用锁、线程池、异常处理和优雅关闭,如使用`volatile`和中断标志。了解这些,能提升程序效率和稳定性。
19 2
|
2月前
|
API
Linux---线程读写锁详解及代码实现
Linux---线程读写锁详解及代码实现
|
2月前
|
存储 Java API
java线程之阻塞队列
java线程之阻塞队列
20 0
|
2月前
|
安全 Java 容器
线程池,定时器以及阻塞队列(生产者/消费者模型)
线程池,定时器以及阻塞队列(生产者/消费者模型)
17 0
|
1天前
|
Java
多线程线程同步
多线程的锁有几种方式
|
8天前
|
调度 Python