Java多线程之等待唤醒机制及案例代码演示

简介: Java多线程之等待唤醒机制及案例代码演示

等待唤醒机制

生产者和消费者是一个十分经典的多线程协作模式

举个小栗子来说明一下消费者和生产者的等待唤醒过程:

常见方法

  • void wait() 当前线程等待,直到被其他线程唤醒
  • void notify() 随机唤醒单个线程
  • void notifyAll() 唤醒所有线程

代码演示

接下来,使用代码来演示生产者和消费者的等待唤醒过程

1、消费者代码:

package com.heima.thread001;
public class FoodThread 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) {
                            e.printStackTrace();
                        }
                    }else {
                        //把吃的总数-1
                        Desk.count--;
                        //如果有,就开吃
                        System.out.println("吃货在吃面条,还能再吃" + Desk.count + "碗");
                        //吃完之后,唤醒厨师继续做
                        Desk.lock.notifyAll();
                        //修改桌子的状态
                        Desk.foodFlag = 0;
                    }
                }
            }
        }
    }
}

2、生产者代码

package com.heima.thread001;
public class CookThread 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();
                    }
                }
            }
        }
    }
}

3、控制生产者和消费者的执行类

package com.heima.thread001;
public class Desk {
    /**
     * 作用:控制生产者和消费者的执行
     */
    //是否有面条  0:没有面条  1:有面条
    public static int foodFlag = 0;
    //总个数
    public static int  count = 10;
    //锁对象
    public static Object lock = new Object();
}

4、测试类

package com.heima.thread001;
public class TestDemo {
    public static void main(String[] args){
        CookThread cookThread = new CookThread();
        FoodThread foodThread = new FoodThread();
        cookThread.setName("厨师");
        foodThread.setName("吃货");
        cookThread.start();
        foodThread.start();
    }
}

5、运行结果

Connected to the target VM, address: '127.0.0.1:52025', transport: 'socket'
Disconnected from the target VM, address: '127.0.0.1:52025', transport: 'socket'
厨师做了一碗面条
吃货在吃面条,还能再吃9碗
厨师做了一碗面条
吃货在吃面条,还能再吃8碗
厨师做了一碗面条
吃货在吃面条,还能再吃7碗
厨师做了一碗面条
吃货在吃面条,还能再吃6碗
厨师做了一碗面条
吃货在吃面条,还能再吃5碗
厨师做了一碗面条
吃货在吃面条,还能再吃4碗
厨师做了一碗面条
吃货在吃面条,还能再吃3碗
厨师做了一碗面条
吃货在吃面条,还能再吃2碗
厨师做了一碗面条
吃货在吃面条,还能再吃1碗
厨师做了一碗面条
吃货在吃面条,还能再吃0碗
Process finished with exit code 0

等待唤醒机制(阻塞队列方式实现)

阻塞队列的继承结构

代码演示:

生产者代码

package com.heima.thread001;
import java.util.concurrent.ArrayBlockingQueue;
public class CookThread extends Thread {
    ArrayBlockingQueue<String> queue;
    public CookThread(ArrayBlockingQueue<String> queue){
        this.queue = queue;
    }
    @Override
    public void run() {
        while (true){
            //不断把面条放到阻塞队列中
            try {
                queue.put("面条");
                System.out.println("厨师放了一碗面条");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

消费者

package com.heima.thread001;
import java.util.concurrent.ArrayBlockingQueue;
public class FoodThread extends Thread {
    ArrayBlockingQueue<String> queue;
    public FoodThread(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();
            }
        }
    }
}

测试类

package com.heima.thread001;
import java.util.concurrent.ArrayBlockingQueue;
public class TestDemo {
    public static void main(String[] args){
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
        CookThread c = new CookThread(queue);
        FoodThread f = new FoodThread(queue);
        c.start();
        f.start();
    }
}

额外扩展

wait()、notify()、notifyAll()方法

Object类里面提供了这几个方法:

wait():让当前线程处于等待(阻塞状态),直到其他线程调用此对象的notify()或notifyAll()方法(进入就绪状态)。

notify():唤醒在此对象监视器上等待的单个线程。

notifyAll():唤醒在此对象监视器上等待的所有线程。

每个方法都有finnal关键字修饰。

为什么这些方法要定义在Object类里,而不定义在Thread类里呢?

因为这些方法的调用必须通过锁对象调用,而锁对象可以是任意对象。所以定义在Object类里。


相关文章
|
1月前
|
JavaScript NoSQL Java
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
202 96
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
|
14天前
|
消息中间件 Java 应用服务中间件
JVM实战—1.Java代码的运行原理
本文介绍了Java代码的运行机制、JVM类加载机制、JVM内存区域及其作用、垃圾回收机制,并汇总了一些常见问题。
JVM实战—1.Java代码的运行原理
|
18天前
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
68 23
|
6天前
|
缓存 Dubbo Java
理解的Java中SPI机制
本文深入解析了JDK提供的Java SPI(Service Provider Interface)机制,这是一种基于接口编程、策略模式与配置文件组合实现的动态加载机制,核心在于解耦。文章通过具体示例介绍了SPI的使用方法,包括定义接口、创建配置文件及加载实现类的过程,并分析了其原理与优缺点。SPI适用于框架扩展或替换场景,如JDBC驱动加载、SLF4J日志实现等,但存在加载效率低和线程安全问题。
理解的Java中SPI机制
|
20天前
|
缓存 运维 Java
Java静态代码块深度剖析:机制、特性与最佳实践
在Java中,静态代码块(或称静态初始化块)是指类中定义的一个或多个`static { ... }`结构。其主要功能在于初始化类级别的数据,例如静态变量的初始化或执行仅需运行一次的初始化逻辑。
35 4
|
22天前
|
传感器 监控 Java
Java代码结构解析:类、方法、主函数(1分钟解剖室)
### Java代码结构简介 掌握Java代码结构如同拥有程序世界的建筑蓝图,类、方法和主函数构成“黄金三角”。类是独立的容器,承载成员变量和方法;方法实现特定功能,参数控制输入环境;主函数是程序入口。常见错误包括类名与文件名不匹配、忘记static修饰符和花括号未闭合。通过实战案例学习电商系统、游戏角色控制和物联网设备监控,理解类的作用、方法类型和主函数任务,避免典型错误,逐步提升编程能力。 **脑图速记法**:类如太空站,方法即舱段;main是发射台,static不能换;文件名对仗,括号要成双;参数是坐标,void不返航。
45 5
|
1月前
|
安全 Java 开发者
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。
|
2月前
|
SQL Java 数据库连接
如何在 Java 代码中使用 JSqlParser 解析复杂的 SQL 语句?
大家好,我是 V 哥。JSqlParser 是一个用于解析 SQL 语句的 Java 库,可将 SQL 解析为 Java 对象树,支持多种 SQL 类型(如 `SELECT`、`INSERT` 等)。它适用于 SQL 分析、修改、生成和验证等场景。通过 Maven 或 Gradle 安装后,可以方便地在 Java 代码中使用。
564 11
|
7月前
|
存储 监控 Java
Java多线程优化:提高线程池性能的技巧与实践
Java多线程优化:提高线程池性能的技巧与实践
190 1
|
10月前
|
设计模式 监控 Java
Java多线程基础-11:工厂模式及代码案例之线程池(一)
本文介绍了Java并发框架中的线程池工具,特别是`java.util.concurrent`包中的`Executors`和`ThreadPoolExecutor`类。线程池通过预先创建并管理一组线程,可以提高多线程任务的效率和响应速度,减少线程创建和销毁的开销。
314 2