草庐IT

【多线程】详解阻塞队列以及常见的面试题

良辰针不戳 2023-06-06 原文

前言:
大家好,我是良辰丫,今天我们来学习一下阻塞队列,这是一种基于线程安全的特殊队列,那么它到底有哪些应用呢?我们慢慢往下看.💞💞💞

🧑个人主页:良辰针不戳
📖所属专栏:javaEE初阶
🍎励志语句:生活也许会让我们遍体鳞伤,但最终这些伤口会成为我们一辈子的财富。
💦期待大家三连,关注,点赞,收藏。
💌作者能力有限,可能也会出错,欢迎大家指正。
💞愿与君为伴,共探Java汪洋大海。

阻塞队列

1. 简述阻塞式队列

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

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

2. 集合里的阻塞队列

首先我们先学习一下集合中的阻塞队列.

public static void main(String[] args) throws InterruptedException {
        BlockingDeque<Integer> blockingDeque = new LinkedBlockingDeque<>();
        blockingDeque.put(1);
        blockingDeque.put(2);
        blockingDeque.put(3);
        blockingDeque.put(4);
        System.out.println(blockingDeque.take());
        System.out.println(blockingDeque.take());
        System.out.println(blockingDeque.take());
        System.out.println(blockingDeque.take());
    }

阻塞队列一个重要的特性就是拥有阻塞状态,一般的队列,take次数大于put次数的时候就会抛出异常;然而我们的阻塞队列,take次数大于put次数的时候,不会报异常,会进入阻塞状态.如下图运行所示.

3. 生产者消费者模型

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者消费者这样的模型是针对某个资源来说的.
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取.

  • 阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力.
  • 阻塞队列也能使生产者和消费者之间 解耦.

下面代码我们来模拟一下生产者和消费者,让生产者, 每隔 1s 生产一个元素,有元素消费者进行消费,没有元素阻塞等待.

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class Test16 {
    public static void main(String[] args) {
        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>();

        // 消费者
        Thread t1 = new Thread(() -> {
            while (true) {
                try {
                    int value = blockingQueue.take();
                    System.out.println("消费元素: " + value);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();

        // 生产者
        Thread t2 = new Thread(() -> {
            int value = 0;
            while (true) {
                try {
                    System.out.println("生产元素: " + value);
                    blockingQueue.put(value);
                    value++;
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t2.start();

      
    }
}

生产者消费者模型是一种非常重要的思想,我们写代码追求低耦合高内聚.低耦合表示各个代码文件有很少的关联,避免一个代码损坏,其余代码收到很大的牵连;高内聚表示一个相关联的代码分类管理,便于查找.

看了上面,大家或许还是不明白耦合性,那么我们再简单介绍一下耦合性.举个简单的例子,我们买一个二手房子,没有通过中介,直接和商家进行交易,结果呢,商家跑路了,这是一个不幸的消息;如果我们有中介公司做保证,商家无论出现怎样的情况,我们都有中介公司为我们承担.因此呢?有中介公司的时候是我们与商家耦合性比较低的情况.这就是解耦.

还有一种解决方案叫削峰填谷,什么意思呢?我们慢慢往下看,如下图,服务器接收客户端的请求,当然,客户端不止一个,如果直接进行通信,可能突然,客户端的请求大量增加,这时候服务器就反应不过来,此时服务器可能就会面临奔溃的的情况.因为服务器没有考虑峰值,那么在这种情况下就会挂了.因为服务器处理每个请求都需要消耗硬件资源(资源包括CPU,硬盘,内存,带宽等),如果资源达到瓶颈,就会出现故障,系统稳定性会带来很大的风险.

我们来看一下削峰填谷方案的本意.

那么我们就可以采用削峰填谷的方式,我们可以采用一个队列帮助服务器提取元素,可以叫做中间代理服务器.中间代理帮服务器承受了压力,这样就使得服务器不容易挂.如下图所示.

4. 模拟实现阻塞队列

  • 实现一个队列(循环队列)
  • 添加线程安全(使用 synchronized 进行加锁控制. )
  • 添加阻塞功能
    put 插入元素的时候, 判定如果队列满了, 就进行 wait. (注意, 要在循环中进行 wait. 被唤醒时不一定队列就不满了, 因为同时可能是唤醒了多个线程),take 取出元素的时候, 判定如果队列为空, 就进行 wait. (也是循环 wait).
class MyBlockingQueue2 {
    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 {
        while (size == items.length) {
            // 队列满了, 插入失败.
            // return;
            this.wait();
        }
        // 把新元素放到 tail 所在位置上
        items[tail] = elem;
        tail++;
        // 万一 tail 达到末尾, 就需要让 tail 从头再来.
        if (tail == items.length) {
            tail = 0;
        }
        // tail = tail % items.length;
        size++;
        this.notify();
    }

    // 出队列
    synchronized public Integer take() throws InterruptedException {
        while (size == 0) {
            // return null;
            this.wait();
        }
        int value = items[head];
        head++;
        if (head == items.length) {
            head = 0;
        }
        size--;
        this.notify();
        return value;
    }
}

public class Test {
    public static void main(String[] args) {
        MyBlockingQueue2 queue = new MyBlockingQueue2();
        // 消费者
        Thread t1 = new Thread(() -> {
            while (true) {
                try {
                    int value = queue.take();
                    System.out.println("消费: " + value);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 生产者
        Thread t2 = new Thread(() -> {
            int value = 0;
            while (true) {
                try {
                    System.out.println("生产: " + value);
                    queue.put(value);
                    value++;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        t1.start();
        t2.start();

    }
}

上述代码空队列阻塞和满队列阻塞不可能同时执行,因为一个队列不可能既是空又是满.

有关【多线程】详解阻塞队列以及常见的面试题的更多相关文章

  1. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  2. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  3. ruby - 分布式事务和队列,ruby,erlang,scala - 2

    我有一个涉及多台机器、消息队列和事务的问题。因此,例如用户点击网页,点击将消息发送到另一台机器,该机器将付款添加到用户的帐户。每秒可能有数千次点击。事务的所有方面都应该是容错的。我以前从未遇到过这样的事情,但一些阅读表明这是一个众所周知的问题。所以我的问题。我假设安全的方法是使用两阶段提交,但协议(protocol)是阻塞的,所以我不会获得所需的性能,我是否正确?我通常写Ruby,但似乎Redis之类的数据库和Rescue、RabbitMQ等消息队列系统对我的帮助不大——即使我实现某种两阶段提交,如果Redis崩溃,数据也会丢失,因为它本质上只是内存。所有这些让我开始关注erlang和

  4. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  5. git使用常见问题(提交代码,合并冲突) - 2

    文章目录git常用命令(简介,详细参数往下看)Git提交代码步骤gitpullgitstatusgitaddgitcommitgitpushgit代码冲突合并问题方法一:放弃本地代码方法二:合并代码常用命令以及详细参数gitadd将文件添加到仓库:gitdiff比较文件异同gitlog查看历史记录gitreset代码回滚版本库相关操作远程仓库相关操作分支相关操作创建分支查看分支:gitbranch合并分支:gitmerge删除分支:gitbranch-ddev查看分支合并图:gitlog–graph–pretty=oneline–abbrev-commit撤消某次提交git用户名密码相关配置g

  6. 阿里云国际版免费试用:如何注册以及注意事项 - 2

    作为新的阿里云用户,您可以50免费试用多种优惠,价值高达1,700美元(或8,500美元)。这将让您了解和体验阿里云平台上提供的一系列产品和服务。如果您以个人身份注册免费试用,您将获得价值1,700美元的优惠。但是,如果您是注册公司,您可以选择企业免费试用,提交基本信息通过企业实名注册验证,即可开始价值$8,500的免费试用!本教程介绍了如何设置您的帐户并使用您的免费试用版。​关于免费试用在我们开始此试用之前,您还必须遵守以下条款和条件才能访问您的免费试用:只有在一年内创建的账户才有资格获得阿里云免费试用。通过此免费试用优惠,用户可以免费试用免费试用活动页面上列出的每种产品一次。如果您有多个帐

  7. Hive SQL 五大经典面试题 - 2

    目录第1题连续问题分析:解法:第2题分组问题分析:解法:第3题间隔连续问题分析:解法:第4题打折日期交叉问题分析:解法:第5题同时在线问题分析:解法:第1题连续问题如下数据为蚂蚁森林中用户领取的减少碳排放量iddtlowcarbon10012021-12-1212310022021-12-124510012021-12-134310012021-12-134510012021-12-132310022021-12-144510012021-12-1423010022021-12-154510012021-12-1523.......找出连续3天及以上减少碳排放量在100以上的用户分析:遇到这类

  8. ruby - 如何让Ruby捕获线程中的语法错误 - 2

    我正在尝试使用ruby​​编写一个双线程客户端,一个线程从套接字读取数据并将其打印出来,另一个线程读取本地数据并将其发送到远程服务器。我发现的问题是Ruby似乎无法捕获线程内的错误,这是一个示例:#!/usr/bin/rubyThread.new{loop{$stdout.puts"hi"abc.putsefsleep1}}loop{sleep1}显然,如果我在线程外键入abc.putsef,代码将永远不会运行,因为Ruby将报告“undefinedvariableabc”。但是,如果它在一个线程内,则没有错误报告。我的问题是,如何让Ruby捕获这样的错误?或者至少,报告线程中的错误?

  9. ruby - 如何在 ruby​​ 中运行后台线程? - 2

    我是ruby​​的新手,我认为重新构建一个我用C#编写的简单聊天程序是个好主意。我正在使用Ruby2.0.0MRI(Matz的Ruby实现)。问题是我想在服务器运行时为简单的服务器命令提供I/O。这是从示例中获取的服务器。我添加了使用gets()获取输入的命令方法。我希望此方法在后台作为线程运行,但该线程正在阻塞另一个线程。require'socket'#Getsocketsfromstdlibserver=TCPServer.open(2000)#Sockettolistenonport2000defcommandsx=1whilex==1exitProgram=gets.chomp

  10. ruby - Rails 开发服务器、PDFKit 和多线程 - 2

    我有一个使用PDFKit呈现网页的pdf版本的Rails应用程序。我使用Thin作为开发服务器。问题是当我处于开发模式时。当我使用“bundleexecrailss”启动我的服务器并尝试呈现任何PDF时,整个过程会陷入僵局,因为当您呈现PDF时,会向服务器请求一些额外的资源,如图像和css,看起来只有一个线程.如何配置Rails开发服务器以运行多个工作线程?非常感谢。 最佳答案 我找到的最简单的解决方案是unicorn.geminstallunicorn创建一个unicorn.conf:worker_processes3然后使用它:

随机推荐