Java多线程:生产者与消费者案例

 更新时间:2021年07月27日 17:06:56   作者:鱼小洲  
这篇文章主要介绍了Java并发编程中的生产者与消费者模型简述,多线程并发是Java编程中最终要的部分之一,需要的朋友可以参考下,希望能给你带来帮助

前言

想象一下生活中哪些是和线程沾边的?饭店炒菜就是一个很好的例子

首先客人要吃菜,前提是厨师要炒好,也就是说,厨师不炒好的话客人是没有饭菜的。这时候,厨师就是一个线程,客人拿菜就是另一个线程。

工具

jdk13,IDEA2019.1.4

知识点

Thread、Runnable、synchronized、面向对象知识(继承、封装、接口、方法重写)、条件判断以及线程的一些其他知识点

设计思路

首先要有两个线程,也就是说要两个类,分别是Producer(生产者)和Consumer(消费者)。

由于我们是模拟厨师与客人之间的互动,也就是说还需要一个类来封装信息:Message(类)。

然后,避免线程之间发生数据混乱的情况,肯定还需要使用synchronized来进行线程同步。

具体步骤

首先我们来一份没有用synchronized的代码,先看看效果:

public class Message {
    private String title;
    private String content;
    Message(){
    };
    public Message(String title, String content) {
        this.title = title;
        this.content = content;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
}
/*
* 定义生产者类Producer
* */
class Producer implements Runnable{
    private Message msg=null;
    public Producer(Message msg) {
        this.msg = msg;
    }
    @Override
    public void run() {
        for (int i=0;i<=50;i++){
            if (i%2==0){
                this.msg.setTitle("喜欢夜雨吗?");
                try {
                    Thread.sleep(100);
                }catch (InterruptedException e){
                    System.out.println(e);
                }
                this.msg.setContent("是的呢!");
            }else {
                this.msg.setTitle("还不关注我吗?");
                try {
                    Thread.sleep(100);
                }catch (InterruptedException e){
                    System.out.println(e);
                }
                this.msg.setContent("好的呢!");
            }
        }
    }
}
/*
* 定义消费者类Consumer
* */
class Consumer implements Runnable{
    private Message msg=null;
    public Consumer(Message msg) {
        this.msg = msg;
    }
    @Override
    public void run() {
        for (int i=0;i<=50;i++){
            try {
                Thread.sleep(100);
            }catch (InterruptedException e){
                System.out.println(e);
            }
            System.out.println(this.msg.getTitle()+"--->"+this.msg.getContent());
        }
    }
}
class TestDemo{
    public static void main(String[] args) {
        Message msg=new Message();
        new Thread(new Producer(msg)).start();
        new Thread(new Consumer(msg)).start();
    }
}

看看运行结果:

在这里插入图片描述

看仔细咯,发生了数据错乱啊,Title与Content没有一一对应欸。咋办?

能咋办,改代码呗。

发生数据混乱的原因完全是因为,生产者线程还没生产的时候,消费者就已经消费了(至于消费的啥我也不知道,我也不敢问啊)。所以造成了数据混乱,不过我们上面说了啊,要使用synchronized来让线程同步一下。但是又会出问题,我们接着往下看

class TestDemo{
    public static void main(String[] args) {
        Message msg=new Message();
        new Thread(new Producer(msg)).start();
        new Thread(new Consumer(msg)).start();
    }
}
class Message{
    private String title,content;
    public synchronized void set(String title,String content){
        this.title=title;
        this.content=content;
    }
    public synchronized void get(){
        try {
            Thread.sleep(1000);
        }catch (InterruptedException e){
            System.out.println(e);
        }
        System.out.println(this.title+"-->"+this.content);
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
}
class Producer implements Runnable{
    private Message msg=null;
    Producer(Message msg){
        this.msg=msg;
    }
    @Override
    public void run() {
        for (int i=0;i<=50;i++){
            if (i%2==0){
                this.msg.set("喜欢夜雨吗?","是的呢!");
            }else {
                this.msg.set("还不关注吗?","好的呢!");
            }
        }
    }
}
class Consumer implements Runnable{
    private Message msg=null;
    Consumer(Message msg){
        this.msg=msg;
    }
    @Override
    public void run() {
        for (int i=0;i<=50;i++){
            this.msg.get();
        }
    }
}

我又重新封装了一些方法,然后运行的时候,wc,数据倒是不混乱了,但是呢,数据重复了一大堆。

在这里插入图片描述

为啥会出现这个问题呢?最后想了一下,会出现这种问题的,就是因为线程的执行顺序的问题。我们想要实现的效果是生产者线程执行了之后,让生产者线程等待,而后让消费者线程执行,等待消费者线程执行完成之后就又让生产者线程继续执行。后来我又查了查官方文档,发现Object类中专门有三个方法是与线程相关的:

方法 描述
public final void wait() throws InterruptedException 线程的等待
public final void notify() 唤醒第一个等待线程
public final void notifyAll()

当我看到这些方法的时候,心里愣了一下,这不就是我们想要的方法吗,用wait()方法来让生产者线程等待,然后运行消费者线程,等消费者线程执行完了之后又让生产者线程去执行。这其中我们用true和false来表示运行开始和运行暂停。

最后我们来看看完成品的代码:

class TestDemo{
    public static void main(String[] args) {
        Message msg=new Message();
        new Thread(new Producer(msg)).start();
        new Thread(new Consumer(msg)).start();
    }
}
class Message extends Exception{
    private String title,content;
    private boolean flag=true;
    // true表示正在生产,不要来取走,因为没由让消费者区走的
    // false表示可以取走,但是不能生产
    public synchronized void set(String title,String content){
        if (this.flag==false){
            try {
                super.wait();
            }catch (InterruptedException e){
                System.out.println(e);
            }
        }
        this.title=title;
        try {
            Thread.sleep(60);
        }catch (InterruptedException e){
            System.out.println(e);
        }
        this.content=content;
        this.flag=true; // 生产完成,修改标志位
        super.notify(); // 唤醒等待线程
    }
    public synchronized void get(){
        if (flag==true) {// 已经生产好了,等待取走
            try {
                super.wait();
            }catch (InterruptedException e){
                System.out.println(e);
            }
        }
        try {
            Thread.sleep(60);
        }catch (InterruptedException e){
            System.out.println(e);
        }
        System.out.println(this.title+"-->"+this.content);
        this.flag=true;
        super.notify();
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
}
class Producer implements Runnable{
    private Message msg=null;
    Producer(Message msg){
        this.msg=msg;
    }
    @Override
    public void run() {
        for (int i=0;i<=50;i++){
            if (i%2==0){
                this.msg.set("喜欢夜雨吗?","是的呢!");
            }else {
                this.msg.set("还不关注吗?","好的呢!");
            }
        }
    }
}
class Consumer implements Runnable{
    private Message msg=null;
    Consumer(Message msg){
        this.msg=msg;
    }
    @Override
    public void run() {
        for (int i=0;i<=50;i++){
            this.msg.get();
        }
    }
}

运行结果我就不贴了,你们自己去测试一下吧…

总结

这个案例完美的呈现了线程以及面向对象知识的综合运用,具有很强的实际操作性

本篇文章就到这里了,希望能给你带来帮助,也希望您能够多多关注脚本之家的更多内容!

相关文章

  • mybatis模糊查询、分页和别名配置的方法

    mybatis模糊查询、分页和别名配置的方法

    这篇文章主要介绍了mybatis模糊查询、分页和别名配置,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09
  • Maven profile实现不同环境的配置管理实践

    Maven profile实现不同环境的配置管理实践

    这篇文章主要介绍了Maven profile实现不同环境的配置管理实践,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09
  • 基于spring AOP @Around @Before @After的区别说明

    基于spring AOP @Around @Before @After的区别说明

    这篇文章主要介绍了基于spring AOP @Around @Before @After的区别说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • 关于ZooKeeper原理剖析

    关于ZooKeeper原理剖析

    ZooKeeper是一种分布式、高可用性的协调服务,主要用于帮助系统避免单点故障和建立可靠的应用程序,它通过Leader、Follower和Observer的角色来协调节点,并提供分布式协作服务和维护配置信息,ZooKeeper在HDFS、YARN、HBase、SmallFS和Kafka等大数据产品中都有广泛的应用
    2025-02-02
  • 详解Java Bellman-Ford算法原理及实现

    详解Java Bellman-Ford算法原理及实现

    Bellman-Ford算法与Dijkstra算法类似,都是以松弛操作作为基础,Bellman-Ford算法是对所有边都进行松弛操作,本文将详解Bellman-Ford算法原理及实现,感兴趣的可以了解一下
    2022-07-07
  • 图文详解SpringBoot中Log日志的集成

    图文详解SpringBoot中Log日志的集成

    这篇文章主要给大家介绍了关于SpringBoot中Log日志的集成的相关资料,文中通过实例代码以及图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2021-12-12
  • Java 入门图形用户界面设计之列表框JList

    Java 入门图形用户界面设计之列表框JList

    图形界面(简称GUI)是指采用图形方式显示的计算机操作用户界面。与早期计算机使用的命令行界面相比,图形界面对于用户来说在视觉上更易于接受,本篇精讲Java语言中关于图形用户界面的列表框JList
    2022-02-02
  • java基础二叉搜索树图文详解

    java基础二叉搜索树图文详解

    二叉树是一种非常重要的数据结构,它同时具有数组和链表各自的特点,下面这篇文章主要给大家介绍了关于java基础二叉搜索树的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-03-03
  • Shiro+Redis实现登录次数冻结的示例

    Shiro+Redis实现登录次数冻结的示例

    这篇文章主要介绍了Shiro+Redis实现登录次数冻结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • SpringBoot+Vue实现数据添加功能

    SpringBoot+Vue实现数据添加功能

    这篇文章主要介绍了SpringBoot+Vue实现数据添加功能,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03

最新评论