JUC多线程编程之生产者与消费者问题(Synchronized和JUC版)

news/2024/5/20 8:54:16 标签: java, 多线程, 并发编程, juc,

生产者与消费者问题

在面试中,生产者与消费者是高频问题之一

1.生产者和消费者问题 Synchronized 版

java">public class A {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "C").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "D").start();
    }
}
// 判断等待,业务,通知
class Data {
     // 数字 资源类
    private int number = 0;
    //+1
    public synchronized void increment() throws InterruptedException {
        /*
        假设 number此时等于1,即已经被生产了产品
        如果这里用的是if判断,如果此时A,C两个生产者线程争夺increment()方法执行权
        假设A拿到执行权,经过判断number!=0成立,则A.wait()开始等待(wait()会释放),然后C试图去执行
        生产方法,但依然判断number!=0成立,则B.wait()开始等待(wait()会释放)
        碰巧这时候消费者线程线程B/D去消费了一个产品,使number=0然后,B/D消费完后调用this.notifyAll();
        这时候2个等待中的生产者线程继续生产产品,而此时number++ 执行了2次
        同理,重复上述过程,生产者线程继续wait()等待,消费者调用this.notifyAll();
        然后生产者继续超前生产,最终导致‘产能过剩’,即number大于1
        if(number != 0){
            // 等待
            this.wait();
        }*/
        while (number != 0) {
     // 注意这里不可以用if 否则会出现虚假唤醒问题,解决方法将if换成while
            // 等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        // 通知其他线程,我+1完毕了
        this.notifyAll();
    }
    //-1
    public synchronized void decrement() throws InterruptedException {
        while (number == 0) {
            // 等待
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        // 通知其他线程,我-1完毕了
        this.notifyAll();
    }
}

这里消费者只有两个,但当消费者数量>2时,就会出现问题
当资源类中方法中判断为if时,执行结果:很明显出现问题了
在这里插入图片描述
当把if改为while时,又没有什么问题了,这是为什么呢?
在这里插入图片描述
这种现象叫作 虚假唤醒,首先来看看官方文档

在这里插入图片描述
接下来我们来看看juc版的生产者与消费者问题

JUC版的生产者和消费者问题
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码例子实现:

java">package sx;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @program: juc
 * @description
 * @author: 不会编程的派大星
 * @create: 2021-04-21 11:03
 **/
public class B {
    public static void main(String[] args) {
        Data2 data = new Data2();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "C").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "D").start();
    }
}
// 判断等待,业务,通知
class Data2 {
    // 数字 资源类
    private int number = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    //condition.await(); // 等待
    //condition.signalAll(); // 唤醒全部
    //+1
    public  void increment() throws InterruptedException {
        lock.lock();
        try {
            // 业务代码
            while (number != 0) {
                // 等待
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            // 通知其他线程,我+1完毕了
            condition.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    //-1
    public  void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (number == 0) {
                // 等待
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            // 通知其他线程,我-1完毕了
            condition.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

运行结果:
在这里插入图片描述
有小伙伴可能会有这个疑问,那既然都可以用,那这个juc的版本又有什么用呢?

任何一个新的技术,绝对不是仅仅只是覆盖了原来的技术,是有其对旧技术的优势和补充!

Condition 精准的通知和唤醒线程
问题:ABCD线程 抢占执行的顺序是随机的,如果想让ABCD线程有序执行,该如何改进代码?

期望实现的效果: A 执行完调用B,B执行完调用C,C执行完调用A

代码:

java">package sx;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @program: juc
 * @description
 * @author: 不会编程的派大星
 * @create: 2021-04-21 11:09
 **/
/*
 * A 执行完调用B,B执行完调用C,C执行完调用A
 */
public class C {
    public static void main(String[] args) {
        Data3 data = new Data3();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                data.printA();
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                data.printB();
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                data.printC();
            }
        }, "C").start();
    }
}
class Data3 {
    // 资源类 Lock
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int number = 1;
    // number=1 A执行  number=2 B执行 number=3 C执行
    public void printA() {
        lock.lock();
        try {
            // 业务,判断-> 执行-> 通知
            while (number != 1) {
                // A等待
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>AAAAAAA");
            // 唤醒,唤醒指定的人,B
            number = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printB() {
        lock.lock();
        try {
            // 业务,判断-> 执行-> 通知
            while (number != 2) {
                // B等待
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>BBBBBBBBB");
            // 唤醒,唤醒指定的人,c
            number = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printC() {
        lock.lock();
        try {
            // 业务,判断-> 执行-> 通知
            // 业务,判断-> 执行-> 通知
            while (number != 3) {
                // C等待
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>CCCCC ");
            // 唤醒,唤醒指定的人,A
            number = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

运行结果:abc有序执行,达到我们期望的效果
在这里插入图片描述
生产者和消费者问题就讲到这里了
欢迎小伙伴们留言讨论!


http://www.niftyadmin.cn/n/1715666.html

相关文章

JUC多线程编程八锁你都知道了吗?关于锁的八个问题

关于锁的八个问题—八锁 前面的文章中留下一个疑问&#xff0c;到底什么是锁&#xff0c;锁到底锁的是谁&#xff1f; 这里我们就用打电话和发短信的例子来距离说明八锁问题—>锁的是对象方法的调用者或者Class模板&#xff08;.class&#xff09; 1、标准情况下&#xff…

使用宝塔部署node项目_宝塔面板如何部署nuxt项目

1、先在本地执行npm run build 命令&#xff0c;然后将项目根目录下除了node_modules文件夹&#xff0c;其他都打包上传到服务器web服务下&#xff0c;注意.nuxt是隐藏文件夹。当然其实不build也可以&#xff0c;在服务上执行sudo npm install 后再执行 run build也一样。然后执…

写入时复制原理和过程以及CopyOnWriteArrayList源码实现的深入剖析

问题疑问 1.为什么要叫写入时复制集合&#xff1f; 2.CopyOnWriteArrayList 实现原理是什么&#xff1f; 3.CopyOnWriteArrayList 和 ArrayList 有什么区别&#xff1f; 4.CopyOnWriteArrayList 复制是怎么进行复制的&#xff1f; 接下来就让我们带着这几个问题&#xff0c;从…

走进callable,来看看callable是怎么一步步勾搭上Thread的!

1.Callable接口 2.Callable与Runnable不同 *1.Callable是java.util.concurrent下的接口&#xff0c;有返回值&#xff0c;可以跑出被检查出的异常 *2Runable是java.lang下的接口&#xff0c;没有返回值&#xff0c;不可以抛出检查出的异常 *3.二者重写调用的方法不同&#xf…

sublime编译python乱码_Sublime Text 3使用virtualenv插件编译时编译结果乱码的问题解决方案...

使用sublime text 3编写python脚本时&#xff0c;平常使用CtrlB的编译系统编译在输出结果中中文就是显示正确的中文&#xff0c;不会显示乱码&#xff0c;但是当我启用了virtualenv插件&#xff0c;使用虚拟环境编译时&#xff0c;却看到中文显示出了乱码。我又在cmd运行python…

JUC的三大常用辅助类,你都知道吗?

1.countDownLatch 减法计数器&#xff1a;实现调用几次线程后&#xff0c;在触发另一个任务 简单代码实现&#xff1a; *举例说明&#xff1a;就像五个人在同一房间里&#xff0c;有一个看门的大爷&#xff0c;当五个人都出去后&#xff0c;他才能锁门&#xff0c;也就是说 执…

ReadWriteLock,读写锁你真的会用吗?

ReadWriteLock 基本介绍 独占锁&#xff08;写锁&#xff09; 一次只能被一个线程占有共享锁&#xff08;读锁&#xff09; 多个线程可以同时占有 ReadWriteLock 读-读 可以共存&#xff01;读-写 不能共存&#xff01;写-写 不能共存&#xff01;可以多个线程同时读&#…

事务的隔离级别举例_一文彻底读懂MySQL事务的四大隔离级别

前言之前分析一个死锁问题&#xff0c;发现自己对数据库隔离级别理解还不够深入&#xff0c;所以趁着这几天假期&#xff0c;整理一下MySQL事务的四大隔离级别相关知识&#xff0c;希望对大家有帮助~事务什么是事务&#xff1f;事务&#xff0c;由一个有限的数据库操作序列构成…