【多线程:锁】生产者消费者

简介: 【多线程:锁】生产者消费者

【多线程:锁】生产者消费者

01.介绍

例子介绍:

这里的例子是生产者消费者模型

我们有一个生产糖果的生产者 与 一个消费糖果的消费者,假设 生产是一个线程 消费是一个线程 总共的糖果池是6,这时会出现两个错误 第一如果我们的消费者的消费速度大于生产者的生产速度 就会出现 把糖果消费到0的时候 生产者还没有生产 但消费者还在消费 导致错误,以及生产速度大于消费速度 就会导致 生产到了6个 但是消费者还没有消费 生产者就再次消费 导致糖果池大于了6个 导致错误。

这个例子说明了什么问题:

这个例子说明了 当两个线程出现类似生产消费等这种关系时 极有可能会出现 某一个线程已经开始不满足接着运行的条件了 但是它还是霸占着cpu 而不是给另外的线程运行,所以我们要解决这个问题,对于上述例子来说,解决方法就是 当生产者生产糖果达到6时就切换到消费线程,当消费线程消费到0时就切换到生产线程。

如何解决:

在多线程中 我们有两个方法,一个是 wait方法 用于使当前线程陷入等待状态,一个是 notify方法 用于唤醒其他线程。

在上述例子中 如果生产速度大于消费速度时 我们就可以在 生产线程 达到糖果为6这个条件时 进行wait使其陷入等待 并使用notify方法 把消费线程唤醒(如果消费线程本来就没有wait就不需要唤醒),之后就可以切换到消费线程运行 直到消费线程消费后使得生产线程不满足达到6个糖果的条件 我们再使用notify唤醒 生产线程。一直这样下去保证了程序可以正常运行。

02.wait、sleep、notify、notifyAll方法的介绍

wait

wait方法是线程等待,这个方法会使拥有对象锁的线程进入等待状态,直到其他线程调用notify或notifyAll方法才能“唤醒”

sleep

sleep方法是线程暂停,是线程用来控制自身流程的,它会使线程暂停一段时间,把执行机会让给其他线程,等计时时间一到,此线程会自动“苏醒”

notify

notify是用来用来唤醒调用wait方法进入等待锁资源队列的线程,唤醒正在等待此对象监视器的单个线程。 如果有多个线程在等待,则选择其中一个随机唤醒(由调度器决定),唤醒的线程享有公平竞争资源的权利

notifyAll

notifyAll是用来用来唤醒调用wait方法进入等待锁资源队列的线程,唤醒正在等待此对象监视器的所有线程,唤醒的所有线程公平竞争资源

原版

class SynStack
{
    private char[] data = new char[6];
    private int cnt = 0;    
    public void push(char ch)
    {
        data[cnt]=ch;
        ++cnt;
        System.out.println("1111");    
    }
    public char pop()
    {
        --cnt;
        System.out.printf("2222");
        return data[cnt];    
    }
}
class Producer implements Runnable
{
    private SynStack ss=null;
    public Producer(SynStack ss)
    {
        this.ss=ss;    
    }
    public void run()// throws Exception //错误 Runnable里没有异常处理
    {
        // push('a');//错误
        try
        {
            Thread.sleep(2000);
        }    
        catch(Exception e)
        {
        }
        ss.push('a');
        
    }
}
class Consumer implements Runnable
{
    private SynStack ss=null;
    public Consumer(SynStack ss)
    {
        this.ss=ss;    
    }
    public void run()
    {
        System.out.printf("%c\n",ss.pop());
    }
}
public class TestPC
{
    public static void main(String[] args)
    {
        SynStack ss = new SynStack();
        Producer p = new Producer(ss);
        Consumer c = new Consumer(ss);    
        Thread t1=new Thread(p);
        Thread t2=new Thread(c);
        t1.start();
        t2.start();//错误 因为无法判断是先进还是先出,如果先进则没错 但如果先出 则现在栈中没有元素 导致数组下标越界
    }    
    
}

结果

报数组溢出错误

解释:

我们观察程序,发现我们创造了一个栈 有两个方法 分别是pop()与push() 出栈与入栈,这个栈其实就是 糖果池 而糖果池的最大容量就是 这个栈的最大值6

之后我们创建了生产者与消费者且他们都实现了Runnable接口与run方法,生产者会在run方法中push()一个元素 也就是增加一个糖果,消费者会在run方法中pop()一个元素 也就是减少一个糖果。

最后我们创建了生产线程与消费线程,我们令生产线程的生产速率小于消费线程,最终会发现当糖果池为0后消费线程还在消费,导致溢出错误。

解决方案

class SynStack
{
    private char[] data = new char[6];
    private int cnt = 0;    
    
    public synchronized void push(char ch)
    {
        while(cnt==data.length)
        //注意:
        //这里用while是为了保证 栈为满而暂停,然后下一次如果再满还是再这个循环里 保证再暂停,
        //如果改成if 则只能暂停一次 下一次从暂停的那个位置继续走 
        
        //但如果是同步过以后则可以用 if,因为如果满 则暂停(wait)生产线程 同时解除霸占,转换到
        //消费线程 进行消费操作 并锁住消费线程对象 导致一定会运行完消费操作 使得栈不为满 之后
        //唤醒生产操作 并结束消费程序 解锁,之后再竞争
        
        // 但这种while与if都可以用的情况 仅在 只有两个相互依赖的线程 的条件下成立,我们可以想一想 如果我们现在来一个毫不相干的线程c c运行完后也
        // 会唤醒某个线程 这样不满足条件的线程依旧可能被唤醒 并且因为是if 所以直接跳出if进行生产操作 导致错误
        
        //使用wait会解锁,sleep则不会
        {
            try
            {
                this.wait();    
            }    
            catch(Exception e)
            {}
        }
        this.notify();//唤醒其他某一个暂停的线程 对于这个例子 就是唤醒消费线程
        data[cnt]=ch;
        ++cnt;
        System.out.printf("生产线程正在生产第%d个产品,该产品是:%c\n",cnt,ch);    
    }
    
    
    public synchronized char pop()
    {
        char ch;
        if(cnt==0)
        //注意:这里的注意同上
        {
            try
            {
                this.wait();    
            }    
            catch(Exception e)
            {}
        }
        this.notify();//唤醒其他某一个暂停的线程 对于这个例子 就是唤醒生产线程
        ch=data[cnt-1];
        System.out.printf("消费线程正在消费第%d个产品,该产品是:%c\n",cnt,ch);    
        cnt--;
        return ch;    
    }
}


class Producer implements Runnable
{ 
    private SynStack ss=null;
    
    public Producer(SynStack ss)
    {
        this.ss=ss;    
    }
    
    public void run()// throws Exception //错误 Runnable里没有异常处理
    {
        // push('a');//错误
        char ch;
        for(int i=0;i<20;i++)
        {
            try
            {
                Thread.sleep(200);//这种是生产比较慢 消费快
            }    
            catch(Exception e)
            {
            }
            ch=(char)('a'+i);
            ss.push(ch);
        }
    }
}


class Consumer implements Runnable
{
    private SynStack ss=null;
    
    public Consumer(SynStack ss)
    {
        this.ss=ss;    
    }
    
    public void run()
    {
        for(int i=0;i<20;i++)
        {
            /*try
            {
                Thread.sleep(200);//这种是消费比较慢 生产快
            }    
            catch(Exception e)
            {
            }*/
            ss.pop();
        }
    }
}


public class TestPC_2
{
    public static void main(String[] args)
    {
        SynStack ss = new SynStack();
        Producer p = new Producer(ss);
        Consumer c = new Consumer(ss);    
        Thread t1=new Thread(p);
        Thread t2=new Thread(c);
        t1.start();
        t2.start();
    }    
}

结果

生产线程正在生产第1个产品,该产品是:a
消费线程正在消费第1个产品,该产品是:a
生产线程正在生产第1个产品,该产品是:b
消费线程正在消费第1个产品,该产品是:b
生产线程正在生产第1个产品,该产品是:c
消费线程正在消费第1个产品,该产品是:c
生产线程正在生产第1个产品,该产品是:d
消费线程正在消费第1个产品,该产品是:d
生产线程正在生产第1个产品,该产品是:e
消费线程正在消费第1个产品,该产品是:e
生产线程正在生产第1个产品,该产品是:f
消费线程正在消费第1个产品,该产品是:f
生产线程正在生产第1个产品,该产品是:g
消费线程正在消费第1个产品,该产品是:g
生产线程正在生产第1个产品,该产品是:h
消费线程正在消费第1个产品,该产品是:h
生产线程正在生产第1个产品,该产品是:i
消费线程正在消费第1个产品,该产品是:i
生产线程正在生产第1个产品,该产品是:j
消费线程正在消费第1个产品,该产品是:j
生产线程正在生产第1个产品,该产品是:k
消费线程正在消费第1个产品,该产品是:k
生产线程正在生产第1个产品,该产品是:l
消费线程正在消费第1个产品,该产品是:l
生产线程正在生产第1个产品,该产品是:m
消费线程正在消费第1个产品,该产品是:m
生产线程正在生产第1个产品,该产品是:n
消费线程正在消费第1个产品,该产品是:n
生产线程正在生产第1个产品,该产品是:o
消费线程正在消费第1个产品,该产品是:o
生产线程正在生产第1个产品,该产品是:p
消费线程正在消费第1个产品,该产品是:p
生产线程正在生产第1个产品,该产品是:q
消费线程正在消费第1个产品,该产品是:q
生产线程正在生产第1个产品,该产品是:r
消费线程正在消费第1个产品,该产品是:r
生产线程正在生产第1个产品,该产品是:s
消费线程正在消费第1个产品,该产品是:s
生产线程正在生产第1个产品,该产品是:t
消费线程正在消费第1个产品,该产品是:t

解释

因为消费线程比较快,所以现在的情况就是生产一个立马就被消费了
目录
相关文章
|
11天前
|
安全 Java 编译器
线程安全问题和锁
本文详细介绍了线程的状态及其转换,包括新建、就绪、等待、超时等待、阻塞和终止状态,并通过示例说明了各状态的特点。接着,文章深入探讨了线程安全问题,分析了多线程环境下变量修改引发的数据异常,并通过使用 `synchronized` 关键字和 `volatile` 解决内存可见性问题。最后,文章讲解了锁的概念,包括同步代码块、同步方法以及 `Lock` 接口,并讨论了死锁现象及其产生的原因与解决方案。
42 10
线程安全问题和锁
|
6天前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
24天前
|
数据采集 存储 安全
如何确保Python Queue的线程和进程安全性:使用锁的技巧
本文探讨了在Python爬虫技术中使用锁来保障Queue(队列)的线程和进程安全性。通过分析`queue.Queue`及`multiprocessing.Queue`的基本线程与进程安全特性,文章指出在特定场景下使用锁的重要性。文中还提供了一个综合示例,该示例利用亿牛云爬虫代理服务、多线程技术和锁机制,实现了高效且安全的网页数据采集流程。示例涵盖了代理IP、User-Agent和Cookie的设置,以及如何使用BeautifulSoup解析HTML内容并将其保存为文档。通过这种方式,不仅提高了数据采集效率,还有效避免了并发环境下的数据竞争问题。
如何确保Python Queue的线程和进程安全性:使用锁的技巧
|
24天前
|
Java 开发者
Java多线程教程:使用ReentrantLock实现高级锁功能
Java多线程教程:使用ReentrantLock实现高级锁功能
23 1
|
29天前
|
消息中间件 设计模式 安全
多线程魔法:揭秘一个JVM中如何同时运行多个消费者
【8月更文挑战第22天】在Java虚拟机(JVM)中探索多消费者模式,此模式解耦生产与消费过程,提升系统性能。通过`ExecutorService`和`BlockingQueue`构建含2个生产者及4个消费者的系统,实现实时消息处理。多消费者模式虽增强处理能力,但也引入线程安全与资源竞争等挑战,需谨慎设计以确保高效稳定运行。
58 2
|
30天前
|
存储 安全 容器
【多线程面试题二十一】、 分段锁是怎么实现的?
这篇文章解释了分段锁的概念和实现方式,通过将数据分成多个段并在每段数据上使用独立锁,从而降低锁竞争,提高并发访问效率,举例说明了`ConcurrentHashMap`如何使用分段锁技术来实现高并发和线程安全。
【多线程面试题二十一】、 分段锁是怎么实现的?
|
17天前
|
安全 Java API
Java线程池原理与锁机制分析
综上所述,Java线程池和锁机制是并发编程中极其重要的两个部分。线程池主要用于管理线程的生命周期和执行并发任务,而锁机制则用于保障线程安全和防止数据的并发错误。它们深入地结合在一起,成为Java高效并发编程实践中的关键要素。
10 0
|
23天前
|
数据采集 Java Python
python 递归锁、信号量、事件、线程队列、进程池和线程池、回调函数、定时器
python 递归锁、信号量、事件、线程队列、进程池和线程池、回调函数、定时器
|
26天前
|
Java 开发者
解锁Java并发编程的秘密武器!揭秘AQS,让你的代码从此告别‘锁’事烦恼,多线程同步不再是梦!
【8月更文挑战第25天】AbstractQueuedSynchronizer(AQS)是Java并发包中的核心组件,作为多种同步工具类(如ReentrantLock和CountDownLatch等)的基础。AQS通过维护一个表示同步状态的`state`变量和一个FIFO线程等待队列,提供了一种高效灵活的同步机制。它支持独占式和共享式两种资源访问模式。内部使用CLH锁队列管理等待线程,当线程尝试获取已持有的锁时,会被放入队列并阻塞,直至锁被释放。AQS的巧妙设计极大地丰富了Java并发编程的能力。
27 0
|
30天前
|
缓存 Java
【多线程面试题二十三】、 说说你对读写锁的了解volatile关键字有什么用?
这篇文章讨论了Java中的`volatile`关键字,解释了它如何保证变量的可见性和禁止指令重排,以及它不能保证复合操作的原子性。