自定义一个线程安全的生产者-消费者模型(大厂java面试题)

生产者-消费者模型的核心思想是通过阻塞队列和线程的等待和通知机制实现生产者和消费者之间的协作,确保生产者不会向满队列中添加消息,消费者不会从空队列中获取消息,从而有效地解决了多线程间的同步问题。

需要实现两个方法。方法1向队列中放入消息,如果队列中的数据满了就线程等待,方法2从队列中拿数据,如果队列空了就等待

  1. 生产者方法:这个方法用于将消息放入队列。如果队列已满,生产者需要等待,直到队列有足够的空间。在等待时,生产者线程通过调用 wait() 方法释放锁,并处于等待状态,直到队列中有足够的空间。一旦队列有空间,其他线程会通过 notify()notifyAll() 方法通知生产者可以继续执行。

  2. 消费者方法:这个方法用于从队列中获取消息。如果队列为空,消费者需要等待,直到队列有消息可供消费。与生产者一样,消费者线程在等待时也会通过 wait() 方法释放锁,并处于等待状态,直到队列中有消息可供消费。一旦有消息可供消费,其他线程会通过 notify()notifyAll() 方法通知消费者可以继续执行。

首先定义一个消息类

/**
 * 消息实体类
 * @date 2024/02/01
 */
final class Message {
    private int id;
    private Object value;

    public Message(int id, Object value) {
        this.id = id;
        this.value = value;
    }

    public int getId() {
        return id;
    }

    public Object getValue() {
        return value;
    }

    @Override
    public String toString() {
        return "Message{" +
                "id=" + id +
                ", value=" + value +
                '}';
    }

然后实现上面的两个方法

/**
 * 利用阻塞队列原理实现
 * @date 2024/02/01
 */
class ZuseQueue {
    /** 消息队列集合*/
    private LinkedList<Message> list = new LinkedList<>();
    /** 消息队列容量*/
    private int capcity;

    /** 存入消息*/
    public void put(Message m){
        synchronized (list) {
            while (list.size() == capcity) {
                try{
                    list.wait();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            // 将消息加入队列尾部
            list.addLast(m);
            list.notifyAll();
        }
    }

    /** 获取消息*/
    public Message take() {
        synchronized (list) {
            while (list.isEmpty()) {
                try{
                    list.wait();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            // 从队列头部获取消息
            Message message = list.removeFirst();
            list.notifyAll();
            return message;
        }
    }

    // 定义队列容量
    public ZuseQueue(int capcity) {
        this.capcity = capcity;
    }
}

之后进行代码测试,这里提供两种方式开启两个线程

  1. 使用线程(Thread)类:在第一种写法中,显式地创建了两个线程(producerThreadconsumerThread),并通过 start() 方法启动它们。这是传统的 Java 多线程编程方式,通过创建并启动线程来执行任务。

  2. 使用 CompletableFuture:在第二种写法中,使用了 Java 8 中的 CompletableFuture 来创建和管理异步任务。CompletableFuture 提供了更高级的异步编程方式,可以更灵活地组合和管理多个异步操作。

性能上的差异主要取决于具体的应用场景和需求。如果需要更高级的控制和管理异步任务,以及更多的组合和操作,使用 CompletableFuture 可能更有优势。它允许你更容易地处理异常、组合多个异步操作、等待所有任务完成等。

如果你的需求比较简单,只是需要创建和管理一些基本的线程,第一种方式可能更简单和直观。但要注意,在某些情况下,使用 CompletableFuture 可能会更具可读性和维护性,因为它提供了更多的工具和方法来处理异步编程中的常见问题。

public class MessageQueue {
    public static void main(String[] args) {
        ZuseQueue zuseQueue = new ZuseQueue(3);
//        Thread producerThread = new Thread(() -> {
//            for (int i = 0; i < 5; i++) {
//                Message message = new Message(i, "a");
//                zuseQueue.put(message);
//                System.out.println("消息存入:" + message.toString());
//            }
//        });
//
//        Thread consumerThread = new Thread(() -> {
//            while (true) {
//                try {
//                    Thread.sleep(1); // 为了不过于频繁的消费
//                    Message message = zuseQueue.take();
//                    System.out.println("消费成功 消费id:" + message.getId());
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//            }
//        });
//
//        producerThread.start();
//        consumerThread.start();
        CompletableFuture<Void> producerFuture = CompletableFuture.runAsync(() -> {
            for (int i = 0; i < 5; i++) {
                Message message = new Message(i, "a");
                zuseQueue.put(message);
                System.out.println("消息存入:" + message.toString());
            }
        });

        CompletableFuture<Void> consumerFuture = CompletableFuture.runAsync(() -> {
            while (true) {
                try {
                    Thread.sleep(1);
                    Message message = zuseQueue.take();
                    System.out.println("消费成功 消费id:" + message.getId());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(producerFuture, consumerFuture);
        combinedFuture.join();

    }
}

完整代码如下:

package test;

import java.util.LinkedList;
import java.util.concurrent.CompletableFuture;

/**
 * @author 小如
 * @date 2024/02/01
 */
public class MessageQueue {
    public static void main(String[] args) {
        ZuseQueue zuseQueue = new ZuseQueue(3);
//        Thread producerThread = new Thread(() -> {
//            for (int i = 0; i < 5; i++) {
//                Message message = new Message(i, "a");
//                zuseQueue.put(message);
//                System.out.println("消息存入:" + message.toString());
//            }
//        });
//
//        Thread consumerThread = new Thread(() -> {
//            while (true) {
//                try {
//                    Thread.sleep(1); // 为了不过于频繁的消费
//                    Message message = zuseQueue.take();
//                    System.out.println("消费成功 消费id:" + message.getId());
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//            }
//        });
//
//        producerThread.start();
//        consumerThread.start();
        CompletableFuture<Void> producerFuture = CompletableFuture.runAsync(() -> {
            for (int i = 0; i < 5; i++) {
                Message message = new Message(i, "a");
                zuseQueue.put(message);
                System.out.println("消息存入:" + message.toString());
            }
        });

        CompletableFuture<Void> consumerFuture = CompletableFuture.runAsync(() -> {
            while (true) {
                try {
                    Thread.sleep(1);
                    Message message = zuseQueue.take();
                    System.out.println("消费成功 消费id:" + message.getId());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(producerFuture, consumerFuture);
        combinedFuture.join();

    }
}

/**
 * 利用阻塞队列原理实现
 * @date 2024/02/01
 */
class ZuseQueue {
    /** 消息队列集合*/
    private LinkedList<Message> list = new LinkedList<>();
    /** 消息队列容量*/
    private int capcity;

    /** 存入消息*/
    public void put(Message m){
        synchronized (list) {
            while (list.size() == capcity) {
                try{
                    list.wait();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            // 将消息加入队列尾部
            list.addLast(m);
            list.notifyAll();
        }
    }

    /** 获取消息*/
    public Message take() {
        synchronized (list) {
            while (list.isEmpty()) {
                try{
                    list.wait();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            // 从队列头部获取消息
            Message message = list.removeFirst();
            list.notifyAll();
            return message;
        }
    }

    // 定义队列容量
    public ZuseQueue(int capcity) {
        this.capcity = capcity;
    }
}

/**
 * 消息实体类
 * @date 2024/02/01
 */
final class Message {
    private int id;
    private Object value;

    public Message(int id, Object value) {
        this.id = id;
        this.value = value;
    }

    public int getId() {
        return id;
    }

    public Object getValue() {
        return value;
    }

    @Override
    public String toString() {
        return "Message{" +
                "id=" + id +
                ", value=" + value +
                '}';
    }
}

测试截图:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/363148.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Aigtek高压功率放大器主要功能是什么

高压功率放大器是一种用于将低电压信号放大到高电压水平的电子设备。它在许多领域中发挥着重要的作用&#xff0c;具有以下主要功能&#xff1a; 信号放大&#xff1a;高压功率放大器的主要功能之一是将低电压信号放大到高电压水平。它能够以较高的增益放大输入信号&#xff0c…

【云原生之kubernetes系列】--污点与容忍

污点与容忍 污点&#xff08;taints)&#xff1a;用于node节点排斥Pod调度&#xff0c;与亲和效果相反&#xff0c;即taint的node排斥Pod的创建容忍&#xff08;toleration)&#xff1a;用于Pod容忍Node节点的污点信息&#xff0c;即node节点有污点&#xff0c;也将新的pod创建…

​亚马逊测评礼品卡撸C采退如何搬砖?

亚马逊测评礼品卡搬砖、撸C是什么&#xff1f; 拿亚马逊礼品卡搬砖来讲&#xff0c;除了汇率差还有佣金。因为盈利的是美刀&#xff0c;因此比我们国内礼品卡的利润更多。比如亚马逊礼品卡&#xff0c;它的折损率比较低&#xff0c;很容易出手&#xff0c;所以是硬通货的存在。…

SD-WAN与MPLS没有取代之说,合适的才最重要

随着企业网络需求的不断增长和变化&#xff0c;SD-WAN&#xff08;软件定义广域网&#xff09;和MPLS&#xff08;多协议标签交换&#xff09;成为企业网络架构中备受关注的两种技术。然而&#xff0c;值得注意的是&#xff0c;并不存在SD-WAN完全取代MPLS或相反的情况。本文将…

SpringMVC实现对网页的访问,在请求控制器中创建处理请求的方法

目录 测试HelloWorld RequestMapping注解 RequestMapping注解的位置 RequestMapping注解的value属性 RequestMapping注解的method属性 SpringMVC支持路径中的占位符&#xff08;重点&#xff09; SpringMVC获取请求参数 1、通过ServletAPI获取 2、通过控制器方法的形参…

Git系列---标签管理

&#x1f4d9; 作者简介 &#xff1a;RO-BERRY &#x1f4d7; 学习方向&#xff1a;致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f4d2; 日后方向 : 偏向于CPP开发以及大数据方向&#xff0c;欢迎各位关注&#xff0c;谢谢各位的支持 目录 1.理解标签2.创建标签…

ThreadX_note:创建线程

ThreadX 创建线程 ThreadX 是一款实时操作系统 (RTOS)&#xff0c;它提供了一套全面的 API&#xff0c;可以用于创建和管理线程。 创建线程 在 ThreadX 中&#xff0c;我们可以使用 tx_thread_create 函数来创建线程。 exam&#xff1a; #include "tx_api.h"/*…

Ansible自动化运维实战

一、abstract简介 ansible是新出现的自动化运维工具&#xff0c;基于Python开发&#xff0c;集合了众多运维工具(puppet、cfengine、chef、func、fabric) 的优点&#xff0c;实现了批量系统配置、批量程序部署、批量运行命令等功能.无客户端。我们要学一些Ansible的安装和一些基…

AI论文指南|ChatGPT在论文讨论部分能起到什么作用?

点击下方▼▼▼▼链接直达AIPaperPass &#xff01; AIPaperPass - AI论文写作指导平台 公众号原文▼▼▼▼&#xff1a; AI论文指南|ChatGPT在论文讨论部分能起到什么作用&#xff1f; 讨论部分是一篇论文的精华所在&#xff0c;也是写作中最难的部分。讨论部分主要是将文章…

单例模式有几种写法?请谈谈你的理解?

为什么有单例模式&#xff1f; 单例模式&#xff08;Singleton&#xff09;&#xff0c;也叫单子模式&#xff0c;是一种常用的软件设计模式。在应用这个模式时&#xff0c;单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个全局对象&#xff0c;这样有利…

coreldraw怎么添加箭头?

使用coreldraw的时候知道箭头在哪里添加吗&#xff1f;下面小编就给大家带来coreldraw箭头添加教程&#xff0c;有需要的小伙伴不要错过哦。 coreldraw添加箭头方法 1、首先选择桌面Coreldraw格式图片。 2、然后点击文件夹按钮打开文件。 3、最后点击上方工具横线&#xff0c…

免费的ChatGPT网站 ( 7个 )

ChatGPT的核心功能是基于用户在输入时的语言或文本生成相应的回复或继续内容。此外&#xff0c;它还能够完成多种任务&#xff0c;如撰写邮件、视频脚本、文案、翻译、代码编写以及撰写论文等。 博主归纳总结了7个国内非常好用&#xff0c;而且免费的chatGPT网站&#xff0c;AI…

qt学习:http+访问百度智能云api实现车牌识别

目录 登录到百度智能云&#xff0c;找到文字识别 完成操作指引 开通 查看车牌识别的api文档 ​编辑​编辑 查看自己应用的api key 查看回应的数据格式 编程步骤 ui界面编辑 添加模块&#xff0c;头文件和定义变量 新建两个类&#xff0c;一个图像Image类&#xff0c…

使用Docker部署WBO白板并结合内网穿透实现远程访问WBO白板

本文主要是如何使用Docker部署WBO白板并实现公网地址远程访问的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首页&#xff1a;CSDN主页放风讲故事 &#x1f304;每日一句&am…

leetcode刷题(剑指offer)54.螺旋矩阵

54.螺旋矩阵 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2,3,6,9,8,7,4,5]示例 2&#xff1a; 输入&#xff1a;ma…

编译和链接哪个才是最“猴急”的呢???

本篇会加入个人的所谓‘鱼式疯言’ ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 我会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人能…

Windows Server 2003 DNS服务器搭建

系列文章目录 目录 系列文章目录 文章目录 前言 一、DNS服务器是什么&#xff1f; 二、配置服务器 1.实验环境搭建 2.服务器搭建 3)安装Web服务器和DNS服务器 4)查看安装是否成功 5)这里直接配置DNS服务器了,Web服务器如何配置我已经发布过了 文章目录 Windows Serve…

从困惑到精通:私域运营领域10大问题全解析!

一、到底什么是“私域” 在探讨私域流量的实际操作之前&#xff0c;品牌需要对“私域”有一个清晰的认识。那么&#xff0c;究竟什么是“私域”呢&#xff1f; 有观点认为&#xff0c;私域是品牌方掌握的用户群体&#xff0c;这些用户可以在品牌的运营、沉淀和变现中发挥重要…

【第二十二课】最短路:dijkstra算法 ( acwing849 / acwing850 / c++ 代码)

目录 dijkstra算法求最短距离步骤 朴素的dijkstra算法---acwing-849 代码如下 代码思路 堆优化版的dijkstra算法---acwing-850 代码如下 关于最短路问题分有好几种类型 &#xff1a; 单源就是指&#xff1a;只求从一个顶点到其他各顶点 多源是指&#xff1a;要求每个顶…

基于springboot+vue的旅游管理系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 研究现状…