单个 java 虚拟机 生产者消费者

news2024/12/23 17:53:08

一、通过 java.lang.Object#wait(),java.lang.Object#notify,java.lang.Object#notifyAll来实现 生产者,消费者    

public abstract class Goods {

    protected String type;

    protected String goodName;

    protected int number;


    public abstract void produce();


    public abstract void consume();

}


public class Producer implements Runnable{

    private Goods goods;

    public Producer(Goods goods){
        this.goods = goods;
    }

    @Override
    public void run() {

        while (true){
            goods.produce();
        }

    }

}


public class Consumer implements Runnable{

    private Goods goods;

    public Consumer (Goods goods){
        this.goods = goods;
    }

    @Override
    public void run() {

        while (true){
            goods.consume();
        }

    }
}
         1.单个生产者,单个消费者
public class OneProducerOneConsumerGoods extends Goods{

    //false 表示货物是空的 可以继续生产
    private volatile boolean flag = false;

    public OneProducerOneConsumerGoods(String type , int beginNumber){
        this.type = type;
        this.number = beginNumber;
    }

    public synchronized void produce(){

        String name = Thread.currentThread().getName();

        //如果不为空  先等待
        if(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        this.goodName = type + ":" + number;
        System.out.println(name + " 生产商品 " + goodName);
        number ++;
        flag = true;
        this.notify();

    }


    public synchronized void consume(){

        String name = Thread.currentThread().getName();

        //如果 flag == false 货物是空的,等待
        if(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println(name + " 消费商品             " + goodName);
        flag = false;
        this.notify();


    }


}
public class OneProducerOneConsumerTest {


    public static void main(String[] args) {

        Goods goods = new OneProducerOneConsumerGoods("面包" , 1);

        Producer producer = new Producer(goods);

        Consumer consumer = new Consumer(goods);

        Thread thread = new Thread(producer);

        Thread thread1 = new Thread(consumer);

        thread.start();
        thread1.start();

    }

}

这种情况没有出现数据安全问题,也没有出现死锁
 OneProducerOneConsumerGoods 是用于单个生产者单个消费者的,这里使用多个生产者和多个消费者,对 OneProducerOneConsumerGoods 进行操作看看会出现什么问题
/**
 * OneProducerOneConsumerGoods 是用于单个生产者单个消费者的
 * 这里使用多个生产者和多个消费者,对 OneProducerOneConsumerGoods 进行操作
 * 看看会出现什么问题
 */
public class MultiProducerMultiConsumerTest {


    public static void main(String[] args) {

        Goods goods = new OneProducerOneConsumerGoods("面包" , 1);

        Producer producer = new Producer(goods);

        Consumer consumer = new Consumer(goods);

        Thread thread0 = new Thread(producer);
        Thread thread1 = new Thread(producer);

        Thread thread2 = new Thread(consumer);
        Thread thread3 = new Thread(consumer);

        thread0.start();
        thread1.start();
        thread2.start();
        thread3.start();

    }


}

 出现了连续生产或者连续消费的现像,出现这种现像的原因是,当线程调用监视器的wait()方法的时候,不紧会放弃cpu的执行权,处于休眠状态,还会释放掉监视器,这样其他线程就可以进入到synchronized 同步代码块中。

        1.解释一下连续生产

        如果 Thread-0 判断 flag == false,生产一个,将flag修改为 true,然后cpu继续执行 Thread-0,下次判断  flag == true ,Thread-0 进入 wait() ,释放cpu的执行权 ,Thread-1 执行,判断  flag == true ,Thread-1 进入 wait(),释放cpu的执行权 ,这时Thread-0 Thread-1 都进入wait(),Thread-2 开始执行,判断  flag == true,进行消费,然后将flag 修改为 false,Thread-2 消费完了,会通过notify()唤醒一个线程,这时候不管时Thread-2 继续执行还是 Thread-3 执行都会进入到 wait(),Thread-2 通过notify() 唤醒 Thread-0 Thread-1 中的一个,假如唤醒了 Thread-0 他不会再去判断 flag 而是直接往下执行,去生产,生产完Thread-0 通过notify() 唤醒一个线程,这个时候如果  Thread-1 被唤醒,他也不会再去判断 flag ,而是直接往下执行,进行生产,这样就发生了连续生产,如果碰巧 Thread-0 和 Thread-1 连续的相互唤醒,就会出现长时间的连续生产

        2.解释一下连续消费

        如果 Thread-2 判断 flag == false 进入wait() ,Thread-3 开始执行,也进入到wait(),这时候Thread-2 Thread-3 都处于wait(),这是Thread-0 开始执行判断flag == false,进行生产 ,将 flag 修改为 true,调用监视器的 notify() 唤醒 Thread-2 Thread-3 中的一个,假如唤醒了Thread-2 ,他不会再去判断 flag 而是直接往下执行去消费,消费完将flag修改为false,调用监视器的 notify() 唤醒一个线程,如果这时正好唤醒了 Thread-3,他不会再判断 flag ,直接往下执行,去消费,这样就发生了连续的消费,如果碰巧 Thread-2 Thread-3 连续的相互唤醒,就会出现长时间的连续消费

     3.发生这两种现像的原因是,不管生产者还是消费者,如果他们 wait() 之后被唤醒,不会再判断 flag ,导致在不该生产的时候进行了生产,不该消费的时候进行了消费

这里对OneProducerOneConsumerGoods 进行修改,被唤醒之后再次判断 flag 

public class MultiProducerMultiConsumerGoods extends Goods{

    //false 表示货物是空的 可以继续生产
    private volatile boolean flag = false;

    public MultiProducerMultiConsumerGoods(String type , int beginNumber){
        this.type = type;
        this.number = beginNumber;
    }

    public synchronized void produce(){

        String name = Thread.currentThread().getName();

        //这里用while循环,如果wait() 被唤醒后,再次判断 flag
        //如果不为空  先等待
        while (flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        this.goodName = type + ":" + number;
        System.out.println(name + " 生产商品 " + goodName);
        number ++;
        flag = true;
        this.notify();


    }


    public synchronized void consume(){

        String name = Thread.currentThread().getName();
        
        //这里用while循环,如果wait() 被唤醒后,再次判断 flag
        //如果 flag == false 货物是空的,等待
        while (!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println(name + " 消费商品             " + goodName);
        flag = false;
        this.notify();


    }


}
public class MultiProducerMultiConsumerTest1 {


    public static void main(String[] args) {

        Goods goods = new MultiProducerMultiConsumerGoods("面包" , 1);

        Producer producer = new Producer(goods);

        Consumer consumer = new Consumer(goods);

        Thread thread0 = new Thread(producer);
        Thread thread1 = new Thread(producer);

        Thread thread2 = new Thread(consumer);
        Thread thread3 = new Thread(consumer);

        thread0.start();
        thread1.start();
        thread2.start();
        thread3.start();

    }


}


发生了死锁的现像

这里举例子再解释一下死锁发生的原因,如果 Thread-2 判断 flag == false 进入wait() ,Thread-3 开始执行,也进入到wait(),这时候Thread-2 Thread-3 都处于wait(),这是Thread-0 开始执行判断flag == false,进行生产 ,将 flag 修改为 true,调用监视器的 notify() 唤醒 Thread-2 Thread-3 中的一个,这时 Thread-0 继续执行,判断 flag == true ,进入wait() ,  然后 Thread-1 进行执行,判断 flag == true ,进入wait() ,这时 Thread-0 Thread-1 都处于wait() , 但是Thread-2 Thread-3 中有一个之前被 Thread-0 唤醒,如果Thread-2 被唤醒,重新判断 flag == true,进行消费,消费完了之后调用监视器的 notify() 唤醒一个线程,正好唤醒 Thread-3,这时 Thread-3 重新判断 flag == true,进入wait(), Thread-2 如果继续执行 也会判断 flag == true,进入wait(),这样 四个线程就全部进入了wait(),形成了死锁。

这里总结死锁的原因,是因为调用监视器的 notify() 只能唤醒一个线程,如果正好唤醒的是本方的一个线程,那么重新判断 flag ,也会进入到 wait(),导致所有线程都wait(),要解决这个问题,就要在唤醒的时候,至少唤醒一个对方的线程,这样重新判断 flag 才不会直接进入 wait().

我们对上面的代码进行修改,每次唤醒都唤醒全部的线程,这样对方的线程也会被唤醒,继续往下执行,形成不断的唤醒对方的效果,就不会死锁了

public class MultiProducerMultiConsumerGoods1 extends Goods{

    //false 表示货物是空的 可以继续生产
    private volatile boolean flag = false;

    public MultiProducerMultiConsumerGoods1(String type , int beginNumber){
        this.type = type;
        this.number = beginNumber;
    }

    public synchronized void produce(){

        String name = Thread.currentThread().getName();

        //如果不为空  先等待
        while (flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }


        this.goodName = type + ":" + number;
        System.out.println(name + " 生产商品 " + goodName);
        number ++;
        flag = true;
        
        //这里唤醒全部的线程
        this.notifyAll();

    }


    public synchronized void consume(){

        String name = Thread.currentThread().getName();

        //如果 flag == false 货物是空的,等待
        while (!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println(name + " 消费商品             " + goodName);
        flag = false;

        //这里唤醒全部的线程
        this.notifyAll();


    }


}
public class MultiProducerMultiConsumerTest2 {


    public static void main(String[] args) {

        Goods goods = new MultiProducerMultiConsumerGoods1("面包" , 1);

        Producer producer = new Producer(goods);

        Consumer consumer = new Consumer(goods);

        Thread thread0 = new Thread(producer);
        Thread thread1 = new Thread(producer);

        Thread thread2 = new Thread(consumer);
        Thread thread3 = new Thread(consumer);

        thread0.start();
        thread1.start();
        thread2.start();
        thread3.start();

    }


}

没有发生 连续生产 连续消费 死锁 等问题

这里可能会有人这样想,一下子把所有的线程都唤醒了,这样对cpu的线程资源和计算资源太浪费了吧,可不可以在多生产者多消费者的情况下,只唤醒一个对方线程,而不是把所有的线程都唤醒呢,这样的想法确实很好,我们尝试可不可以用两个锁,一个专门用于生产线程的同步,一个专门用于消费线程的同步,我先把这个想法的代码写出来,能不能用再说

public class MultiProducerMultiConsumerGoods2 extends Goods{

    //false 表示货物是空的 可以继续生产
    private volatile boolean flag = false;

    //本来想法是专门限制生产的锁
    private Object produceLock = new Object();
    //本来想法是专门限制消费的锁
    private Object consumeLock = new Object();

    public MultiProducerMultiConsumerGoods2(String type , int beginNumber){
        this.type = type;
        this.number = beginNumber;
    }

    public void produce(){

        String name = Thread.currentThread().getName();

        //生产线程获取生产锁,然后进入进行生产或者 等待
        synchronized(this.produceLock) {
            //如果不为空  先等待
            while (flag) {
                try {
                    this.produceLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }


            this.goodName = type + ":" + number;
            System.out.println(name + " 生产商品 " + goodName);
            number++;
            flag = true;

            //本来想法是生产完了,通过消费锁唤醒一个消费线程,避免唤醒本方线程 
            //和 对方过多的线程
            this.consumeLock.notify();

        }

    }


    public void consume(){

        String name = Thread.currentThread().getName();

        //消费线程获取消费锁,然后进入进行消费或者 等待
        synchronized(this.consumeLock) {
            //如果 flag == false 货物是空的,等待
            while (!flag) {
                try {
                    this.consumeLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println(name + " 消费商品             " + goodName);
            flag = false;

            //本来想法是消费完了,通过生产锁唤醒一个生产线程,避免唤醒本方线程 
            //和 对方过多的线程
            this.produceLock.notify();

        }
    }


}
public class MultiProducerMultiConsumerTest3 {


    public static void main(String[] args) {

        Goods goods = new MultiProducerMultiConsumerGoods2("面包" , 1);

        Producer producer = new Producer(goods);

        Consumer consumer = new Consumer(goods);

        Thread thread0 = new Thread(producer);
        Thread thread1 = new Thread(producer);

        Thread thread2 = new Thread(consumer);
        Thread thread3 = new Thread(consumer);

        thread0.start();
        thread1.start();
        thread2.start();
        thread3.start();

    }


}


Thread-0 生产商品 面包:1
Thread-3 消费商品             面包:1
Exception in thread "Thread-0" Exception in thread "Thread-3" java.lang.IllegalMonitorStateException
	at java.lang.Object.notify(Native Method)
	at com.fll.test.multi_thread.producer_consumer.MultiProducerMultiConsumerGoods2.produce(MultiProducerMultiConsumerGoods2.java:40)
	at com.fll.test.multi_thread.producer_consumer.Producer.run(Producer.java:15)
	at java.lang.Thread.run(Thread.java:748)
java.lang.IllegalMonitorStateException
	at java.lang.Object.notify(Native Method)
	at com.fll.test.multi_thread.producer_consumer.MultiProducerMultiConsumerGoods2.consume(MultiProducerMultiConsumerGoods2.java:66)
	at com.fll.test.multi_thread.producer_consumer.Consumer.run(Consumer.java:15)
	at java.lang.Thread.run(Thread.java:748)


我们可以看到报出了 IllegalMonitorStateException , 
因为一个synchronized代码块只能指定一个监视器,
并且当一个线程在获取到监视器进入同步代码块里面的时候,
只能调用所进入的synchronized代码块所指定的监视器的 wait() notify() notifyAll(), 
所以当生产线程获取到生产监视器 produceLock,进入synchronized 代码块,
生产完了调用 consumeLock 的 notify() 的时候就会报错

看来通过 synchronized 无法实现只唤醒对方线程的操作,但是jdk1.5 出来新的API,Lock 锁提供了相应的实现,我们先来看看代码怎么实现
 

public class MultiProducerMultiConsumerGoods3 extends Goods{

    //false 表示货物是空的 可以继续生产
    private volatile boolean flag = false;

    private Lock lock = new ReentrantLock();
    // 同一个Lock锁对象可以创建多个与其关联的 监视器对象
    
    //专门限制生产的 监视器
    private Condition produceCondition = lock.newCondition();
    //专门限制消费的 监视器
    private Condition consumeCondition = lock.newCondition();

    public MultiProducerMultiConsumerGoods3(String type , int beginNumber){
        this.type = type;
        this.number = beginNumber;
    }

    public void produce(){

        String name = Thread.currentThread().getName();

        //生产线程获取生产锁,然后进入进行生产或者 等待
        lock.lock();

        try {
            //如果不为空  先等待
            while (flag) {
                try {
                    //await() 方法和 Object 的wait() 都会释放cpu执行权
                    //并且会释放锁
                    this.produceCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            this.goodName = type + ":" + number;
            System.out.println(name + " 生产商品 " + goodName);
            number++;
            flag = true;

            //生产完了,通过消费 监视器 唤醒一个消费线程,避免唤醒本方线程 和 对方过多的线程
            this.consumeCondition.signal();

        }finally {
            lock.unlock();
        }

    }


    public void consume(){

        String name = Thread.currentThread().getName();

        //消费线程获取消费锁,然后进入进行消费或者 等待
        lock.lock();

        try {
            //如果 flag == false 货物是空的,等待
            while (!flag) {
                try {
                    //await() 方法和 Object 的wait() 都会释放cpu执行权
                    //并且会释放锁
                    this.consumeCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println(name + " 消费商品             " + goodName);
            flag = false;

            //消费完了,通过生产 监视器 醒一个生产线程,避免唤醒本方线程 和 对方过多的线程
            this.produceCondition.signal();

        }finally {
            lock.unlock();
        }

    }


}
public class MultiProducerMultiConsumerTest4 {


    public static void main(String[] args) {

        Goods goods = new MultiProducerMultiConsumerGoods3("面包" , 1);

        Producer producer = new Producer(goods);

        Consumer consumer = new Consumer(goods);

        Thread thread0 = new Thread(producer);
        Thread thread1 = new Thread(producer);

        Thread thread2 = new Thread(consumer);
        Thread thread3 = new Thread(consumer);

        thread0.start();
        thread1.start();
        thread2.start();
        thread3.start();

    }


}

没有出现任何问题,说明这种解决方案是可以的,也是目前看来最好的解决方案

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

redis高级教程

一 关系型数据库和 NoSQL 数据库 数据库主要分为两大类:关系型数据库与 NoSQL 数据库 关系型数据库 ,是建立在关系模型基础上的数据库,其借助于集合代数等数学概念和方法来处理数据库中的数据主流的 MySQL 、 Oracle 、 MS SQL Server 和 D…

基于SpringBoot+Vue+MySQL的美术馆管理系统

系统展示 用户前台界面 管理员后台界面 系统背景 随着文化艺术产业的蓬勃发展,美术馆作为展示与传播艺术的重要场所,其管理工作变得日益复杂。为了提升美术馆的运营效率、优化参观体验并加强艺术品管理,我们开发了基于SpringBootVueMySQL的美…

树莓派!干农活!

农作物种植是一个需要精准操作的行业,而农业的长期趋势是朝着机械化方向发展。Directed Machines公司的土地护理机器人(Land Care Robot),基于Raspberry Pi4和RP2040构建,是解放稀缺人力资本的一种经济高效方式。 Dir…

墨西哥海外仓市场如何?如何选择墨西哥海外仓系统?

随着全球电商市场的迅猛发展,墨西哥作为拉美地区的重要市场,其电商增速在2023年高达24.6%,位居世界第一,这一数据无疑展示了墨西哥电商市场的巨大潜力和繁荣景象。 作为拉美地区最大的电商平台,美客多在墨西哥市场的扩…

iPhone 16分辨率,屏幕尺寸,PPI 详细数据对比 iPhone 16 Plus、iPhone 16 Pro、iPhone 16 Pro Max

史上最全iPhone 机型分辨率,屏幕尺寸,PPI详细数据!已更新到iPhone 16系列! 点击放大查看高清图 !

传承中华文脉·弘扬北疆文化“四季内蒙古演出季”区内外文艺院团交流演出活动即将启动

为推进“北疆文化”品牌建设,由内蒙古自治区文化和旅游厅、呼和浩特市人民政府主办,呼和浩特市文化旅游广电局承办的传承中华文脉弘扬北疆文化——“四季内蒙古演出季”区内外文艺院团交流演出活动将于9月14日至11月期间在呼和浩特市举办。 传承中华文脉…

新书推荐:《智人之上:AI时代的信息网络简史》——尤瓦尔·赫拉利的深刻哲学警示

导言:AI革命的到来与历史性的深思 随着人工智能(AI)的快速发展,越来越多的学者、科学家和哲学家开始反思AI带来的潜在威胁与机遇。以色列著名历史学家尤瓦尔赫拉利(Yuval Noah Harari),以其广受…

用Kimi输出流程图

1.输入 我希望设计一个ERP系统,请帮我简单列一个流程图,用mermaid输出2.输出

电脑重装系统后硬盘数据可以恢复吗?系统重装后以前的文件怎么找回来?

重装系统是指对电脑的操作系统例如Windows重新安装。系统重装可以解决各种系统问题,例如电脑感染病毒、系统文件受损、系统变慢、崩溃无法启动、蓝屏等。正常的重装系统操作是将原来的系统分区(通常是C盘)格式化,然后再重新安装Wi…

opencv学习:calcHist 函数绘制图像直方图及代码实现

cv2.calcHist 函数是 OpenCV 库中用于计算图像直方图的函数。直方图是一种统计图像中像素值分布的工具,它可以提供图像的亮度、颜色等信息。这个函数可以用于灰度图像和彩色图像。 函数语法 hist cv2.calcHist(images, channels, mask, histSize, ranges, accumu…

IO中断原理浅析

目录 什么是中断 什么是IO中断 无中断的情况 有中断的情况 什么是中断 中断是指,在程序运行过程中,系统出现一个必须由CPU立即处理的情况,此时CPU暂时中止程序的执行转而处理这个新情况的过程叫做中断。 什么是IO中断 I/O中断通过中断处理…

操作系统的重点笔记-1

一、操作系统的设计目标 1.易用性 使计算机易于使用,提供文件抽象后,对文件的操作就是对磁盘的操作,不再需要考虑如何通过控制磁盘移动,实现对磁盘某个信号的读写细节 2.高效性 完成特定功能的效率,如时间效率&…

音视频开发之旅(93)-图像超分增强之Real-ESRGAN

目录 1、背景和问题 2、高清-低清 数据集构建 3、Real-ESRGAN模型结构 4、源码分析 5、不足与局限性 6、资料 一、背景和问题 图像超分一直是一个活跃的研究课题,旨在从低分辨率(LR)重建高分辨率(HR)图像。在数…

中秋节了,送大家一个月饼

按F12,直接在浏览器控制台输入下面代码,你就会得到下面的月饼 console.log("%c\uD83E\uDD6E","font-size: 20em")

pandas中基于范围条件进行表连接

来自:Python大数据分析 费弗里 表连接是我们日常开展数据分析过程中很常见的操作,在pandas中基于join()、merge()等方法,可以根据左右表连接依赖字段之间对应值是否相等,来实现常规的表连接。 但在有些情况下,我们可能…

JDBC与MyBatis:数据库访问技术的变迁【后端 15】

JDBC与MyBatis:数据库访问技术的变迁 JDBC的基本使用 Java Database Connectivity (JDBC) 是Java提供的一种标准API,用于与数据库进行交互。它提供了一系列的接口和类,使得开发人员能够直接使用Java代码来编写SQL语句并执行数据库操作。JDBC…

Sequential的使用和搭建实战

一、Sequential 是什么 Sequential 主要出现在 Keras 库中,这是一个用于构建和训练深度学习模型的高级 API。Sequential 类允许你按顺序构建神经网络模型,其中每一层都按照给定的顺序逐层堆叠。这种模型适用于大多数线性堆叠的神经网络结构。Sequential…

GEE 迭代删除谷歌资产文件夹

在Google Earth Engine (GEE) 中管理大量地理空间数据时,我们可能会遇到需要清理不再需要的资产的情况。但需要提前删除子文件后才可删除文件夹,才可释放存储空间,删除过时的数据。本文将介绍如何在GEE中迭代删除资产文件夹。 代码详解 以下…

3个方法教大家如何在Excel表格中添加水印

在Excel表格中添加水印是一种常见的需求,但是Excel并没有像word文档一样的直接添加水印的功能,怎么办? 今天小编来分享一个方法,也能实现Excel表格的添加水印~ 一、使用文本框插入文字水印 1、插入选项卡 进入excel的操作界面&…

Google大数据架构技术栈

数据存储层 Colossus Colossus作为Google下一代GFS(Google File System)。 GFS本身存在一些不足 单主瓶颈 GFS 依赖单个主节点进行元数据管理,随着数据量和访问请求的增长,出现了可扩展性瓶颈。想象一下,只有一位…