多线程的等待唤醒机制的自我理解

简介: 多线程的等待唤醒机制的自我理解


核心思路

我们假设有生产者和消费者

用桌子上的共享数据控制线程的执行

消费者先拿到CPU的执行权 就会wait

生产者后拿到CPU的执行权 唤醒消费者

生产者等待

是因为没有消费者

生产者先抢到CPU的执行权

然后等待消费者

然后消费者没有出现 生产者又抢到了CPU的执行权

就会wait

lock锁两个关键方法

wait()方法

在Java中,wait()方法是用于线程间通信和协作的关键方法之一。当一个线程调用某个对象的wait()方法时,它会释放对象的锁,并进入等待状态,直到其他线程调用相同对象的notify()notifyAll()方法来唤醒它。

具体来说,当一个线程调用wait()方法时,它会暂时让出对象的锁,允许其他线程获取该对象的锁并执行。同时,调用wait()方法的线程会进入等待队列,等待其他线程通过调用notify()notifyAll()来唤醒它。

wait()方法通常与synchronized关键字一起使用,以确保线程在进入等待状态前能够正确释放对象的锁。这种机制可以用于实现线程之间的协作,例如等待某个条件满足后再继续执行。

需要注意的是,在调用wait()方法前,线程必须先获得对象的监视器(即锁),否则会抛出IllegalMonitorStateException异常。

notifyAll()方法

在Java中,notifyAll()方法是用于多线程编程中的对象监视器方法之一。当一个线程调用某个对象的notifyAll()方法时,它会唤醒所有正在等待这个对象监视器(即锁)的线程。

具体来说,当一个线程调用某个对象的notifyAll()方法时,该对象上所有因调用wait()方法而被阻塞的线程都会被唤醒,这些线程将会开始竞争对象锁。然后,只有一个线程可以获取到对象锁并继续执行,其他线程将继续等待或者重新竞争锁。

这个方法通常用于实现线程间的协作,让等待某个条件满足的线程能够及时得到通知并继续执行。需要注意的是,在使用notifyAll()时,要确保唤醒的线程都能正确处理唤醒信号,避免出现死锁或者竞态条件等问题。

自我理解

规则是消费者只能吃10碗面条

假设我是消费者

如果是我抢到了线程 我首先先判断是否已经达到了十碗面条的量 用变量记录

然后向下执行

看桌上有没有面条

如果没有面条 抛出锁的对象 让厨师进入线程 做面条

如果有面条 进行核心处理 打印输出我已经吃了 并且进行标记处理标记此时已经没有面条了

如果我是厨师

我先抢到了线程 我首先也是判断是否达到了十碗面条的数量

然后向下执行

如果桌子上有面条 抛出锁的对象 让消费者进入线程 吃面条

如果没有面条 就做面条 标记此时已经有面条了 做完后唤醒消费者线程 此时消费者得进来吃面条

关键

静态变量 两个线程共享的

同时得设置标记 比如说桌面上有无面条

如何标记记录 核心执行逻辑

要把各个方面都考虑

例如 我作为消费者吃了面条 不仅桌面上没有面条了 而且面条计数还要减1

而且我们还要唤醒厨师线程

如何设计结构

我们首先判断的是大条件 就是有没有吃到10碗 否则直接退出

接下来是有没有面条 你等我 我等你

代码实现

public class Desk {
    //共享的数据
    public static int foodFlag=0;//表示桌面上是否有面条 0表示没有面条 1表示有面条
    public static int count=10;//表示面条的总个数
    public static Object lock=new Object();//锁
 
 
}
public class Foodie extends Thread{
    @Override
    public void run() {
        /*
        * 循环
        * 同步代码块
        * 判断共享数据是否到达了末尾
        * 优先处理到达了末尾
        * 随后处理未到达末尾的 执行核心逻辑
        * */
        while(true){//循环
            synchronized (Desk.lock){
                if(Desk.count==0)break;
                else {
                    //先判断桌子上是否有面条
                    if(Desk.foodFlag==0){
                        //没有面条 线程等待
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    else {
                        //有面条 吃货得吃掉面条
                        System.out.println("吃货吃了面条,还能再吃"+(Desk.count-1)+"碗");
                        //唤醒厨师
                        Desk.lock.notifyAll();
                        //总数减去1
                        Desk.count--;
                        //修改桌子的状态 此时是没有面条的了
                        Desk.foodFlag=0;
                    }
                }
            }
        }
    }
}
public class Cook extends Thread{
    @Override
    public void run() {
        /*
        * 循环
        * 同步代码块
        * 判断共享数据是否到达了末尾
        * 先写到达末尾了
        * 在写没有到达末尾的 执行核心逻辑
        * */
        while (true){
            synchronized (Desk.lock){
                if(Desk.count==0)break;
                else {
                    if(Desk.foodFlag==1){//代表桌面上有 这时应该等待
                        try {
                            Desk.lock.wait();//让出锁的对象
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    else {
                        //桌面上没有食物 执行核心逻辑
                        System.out.println("厨师做了一碗面条");
                        //修改标记
                        Desk.foodFlag=1;
                        //此时吃货线程在等待 唤醒吃货线程
                        //唤醒正在等待该锁的所有线程 但是只能抛出一把锁的对象
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}
public class Main {
    public static void main(String[] args) {
        //创建线程
        Foodie f=new Foodie();
        Cook c=new Cook();
 
        //给线程设置名字
        f.setName("吃货");
        c.setName("厨师");
 
        //一起启动
        f.start();
        c.start();
    }
}

输出

阻塞队列方式实现

阻塞队列表示连接消费者和生产者之间的管道

可以规定管道内最多放多少面条

(图片来自黑马程序员)

生产者和消费者必须使用同一个阻塞队列

在put方法当中

底层

会用lock锁的方式把方法锁起来

先获取锁的对象 获取锁 循环判断 判断队列里的元素个数和队列长度是否相等

如果满了就会等待 没有满就会里面添加元素

最后在调用unlock 释放锁

所以我们在书写时就不用写锁

take方法底层也是有锁的

实际效果

所以这种方式会简洁

实际上就是借助了阻塞队列这个容器

而阻塞队列底层有锁结构 可以降低我们书写代码的数量

这种方式关键在于要用一个阻塞队列 是通过书写构造方法实现的

在测试类里往里面传入参数 而这个参数是测试类创建的阻塞队列

import java.util.concurrent.ArrayBlockingQueue;
public class Cook extends Thread{
    ArrayBlockingQueue<String>queue;
 
    //生成构造函数 创建对象的时候给queue赋值
    public Cook(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }
 
    @Override
    public void run() {
        //不断的从阻塞队列中添加面条
        while (true){
            try {
                queue.put("面条");
                System.out.println("厨师放了一碗面条");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
import java.util.concurrent.ArrayBlockingQueue;
public class Foodie extends Thread{
    ArrayBlockingQueue<String> queue;
 
    //生成构造函数 创建对象的时候给queue赋值
    public Foodie(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }
    
    @Override
    public void run() {
        while (true){
            //不断的从阻塞队列中获取面条
            try {
                String food=queue.take();
                System.out.println(food);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
import javax.crypto.spec.PSource;
import java.util.concurrent.ArrayBlockingQueue;
 
public class Main {
    public static void main(String[] args) {
        //阻塞队列的对象创建在测试类里面
        //有界的阻塞队列 创建对象的时候指定上限
        ArrayBlockingQueue<String>queue=new ArrayBlockingQueue<>(1);
 
        //创建线程对象 并且把阻塞队列传递过去
        Cook c=new Cook(queue);
        Foodie f=new Foodie(queue);
 
        c.start();
        f.start();
    }
}

最后打印输出结果

个人号推广

博客主页

多多!-CSDN博客

Web后端开发

https://blog.csdn.net/qq_30500575/category_12624592.html?spm=1001.2014.3001.5482

Web前端开发

https://blog.csdn.net/qq_30500575/category_12642989.html?spm=1001.2014.3001.5482

数据库开发

https://blog.csdn.net/qq_30500575/category_12651993.html?spm=1001.2014.3001.5482

项目实战

https://blog.csdn.net/qq_30500575/category_12699801.html?spm=1001.2014.3001.5482

算法与数据结构

https://blog.csdn.net/qq_30500575/category_12630954.html?spm=1001.2014.3001.5482

计算机基础

https://blog.csdn.net/qq_30500575/category_12701605.html?spm=1001.2014.3001.5482

回忆录

https://blog.csdn.net/qq_30500575/category_12620276.html?spm=1001.2014.3001.5482


目录
相关文章
|
4月前
|
Java 程序员 开发者
深入理解Java并发编程:线程同步与锁机制
【4月更文挑战第30天】 在多线程的世界中,确保数据的一致性和线程间的有效通信是至关重要的。本文将深入探讨Java并发编程中的核心概念——线程同步与锁机制。我们将从基本的synchronized关键字开始,逐步过渡到更复杂的ReentrantLock类,并探讨它们如何帮助我们在多线程环境中保持数据完整性和避免常见的并发问题。文章还将通过示例代码,展示这些同步工具在实际开发中的应用,帮助读者构建对Java并发编程深层次的理解。
|
3月前
|
Java
并发编程的艺术:Java线程与锁机制探索
【6月更文挑战第21天】**并发编程的艺术:Java线程与锁机制探索** 在多核时代,掌握并发编程至关重要。本文探讨Java中线程创建(`Thread`或`Runnable`)、线程同步(`synchronized`关键字与`Lock`接口)及线程池(`ExecutorService`)的使用。同时,警惕并发问题,如死锁和饥饿,遵循最佳实践以确保应用的高效和健壮。
43 2
|
4月前
|
存储 安全 Java
深入理解Java并发编程:线程安全与锁机制
【5月更文挑战第31天】在Java并发编程中,线程安全和锁机制是两个核心概念。本文将深入探讨这两个概念,包括它们的定义、实现方式以及在实际开发中的应用。通过对线程安全和锁机制的深入理解,可以帮助我们更好地解决并发编程中的问题,提高程序的性能和稳定性。
|
4月前
|
缓存 安全 Java
7张图带你轻松理解Java 线程安全,java缓存机制面试
7张图带你轻松理解Java 线程安全,java缓存机制面试
|
25天前
|
安全 Java API
Java线程池原理与锁机制分析
综上所述,Java线程池和锁机制是并发编程中极其重要的两个部分。线程池主要用于管理线程的生命周期和执行并发任务,而锁机制则用于保障线程安全和防止数据的并发错误。它们深入地结合在一起,成为Java高效并发编程实践中的关键要素。
11 0
|
28天前
探索操作系统中的线程同步机制
【8月更文挑战第31天】在多线程编程领域,理解并实现线程同步是至关重要的。本文通过浅显易懂的语言和生动的比喻,带你走进线程同步的世界,从互斥锁到信号量,再到条件变量,逐步揭示它们在协调线程行为中的作用。我们将一起动手实践,用代码示例加深对线程同步机制的理解和应用。
|
2月前
|
调度
【浅入浅出】Qt多线程机制解析:提升程序响应性与并发处理能力
在学习QT线程的时候我们首先要知道的是QT的主线程,也叫GUI线程,意如其名,也就是我们程序的最主要的一个线程,主要负责初始化界面并监听事件循环,并根据事件处理做出界面上的反馈。但是当我们只限于在一个主线程上书写逻辑时碰到了需要一直等待的事件该怎么办?它的加载必定会带着主界面的卡顿,这时候我们就要去使用多线程。
123 6
|
2月前
|
存储 前端开发 Java
(二)JVM成神路之剖析Java类加载子系统、双亲委派机制及线程上下文类加载器
上篇《初识Java虚拟机》文章中曾提及到:我们所编写的Java代码经过编译之后,会生成对应的class字节码文件,而在程序启动时会通过类加载子系统将这些字节码文件先装载进内存,然后再交由执行引擎执行。本文中则会对Java虚拟机的类加载机制以及执行引擎进行全面分析。
|
2月前
|
Java 调度
Java中的线程池机制详解
Java中的线程池机制详解
|
3月前
|
Java
Java中的`synchronized`关键字是一个用于并发控制的关键字,它提供了一种简单的加锁机制来确保多线程环境下的数据一致性。
【6月更文挑战第24天】Java的`synchronized`关键字确保多线程数据一致性,通过锁定代码块或方法防止并发冲突。同步方法整个方法体为临界区,同步代码块则锁定特定对象。示例展示了如何在`Counter`类中使用`synchronized`保证原子操作和可见性,同时指出过度使用可能影响性能。
35 4