java干货 线程间通信

文章目录

    • 一、线程间通信
      • [1.1 为什么要处理线程间通信?](#1.1 为什么要处理线程间通信?)
      • [1.2 什么是等待唤醒机制?](#1.2 什么是等待唤醒机制?)
    • 二、等待唤醒机制使用
      • [2.1 等待唤醒机制用到的方法](#2.1 等待唤醒机制用到的方法)
        • [2.1.1 wait](#2.1.1 wait)
        • [2.1.2 notify](#2.1.2 notify)
      • [2.2 线程通信代码实践](#2.2 线程通信代码实践)
        • [2.2.1 重要说明](#2.2.1 重要说明)
        • [2.2.2 代码](#2.2.2 代码)

一、线程间通信

1.1 为什么要处理线程间通信?

  • 在默认情况下,cpu是默认切换线程来执行的,当我们需要多个线程共同来完成一个任务,希望他们按照一定的规律执行,那么就需要他们进行通信协调,以达到我们的目的
  • 其次,对于共享变量的访问,我们通常加上synchronized,存在锁的竞争,我们也可以使用等待唤醒机制,协调线程对变量的访问,保证数据的一致性

1.2 什么是等待唤醒机制?

说到线程,我们常提到线程之间的竞争,如多线程下锁的竞争。好比在公司里你和同事在晋升时的竞争,但是多数情况下,还是你们合作共同完成一些任务。也就是一个线程进行了一定操作后,进入等待状态 (waiting),等待其他线程完成任务后将他唤醒(notify)。还有就是当需要等待多个线程时,可以使用notifyAll(),将等待中的线程全部唤醒

二、等待唤醒机制使用

2.1 等待唤醒机制用到的方法

2.1.1 wait
  • wait() 使当前线程进入等待状态,直到被其他线程使用 notify() 或 notifyAll() 唤醒,无限期等待,直到被唤醒。使用场景: 当线程需要等待某个条件变化时使用,无需考虑超时。

    public final void wait() throws InterruptedException {
    wait(0L);
    }

  • wait(long timeoutMillis),本质上调用的还是这个native 方法

    public final native void wait(long timeoutMillis);

  • wait(long timeoutMillis, int nanos) 可以被其他线程调用 notify() 或 notifyAll() 来唤醒,也可以在超时后自动唤醒。

    public final void wait(long timeoutMillis, int nanos) throws InterruptedException {
    if (timeoutMillis < 0) {
    throw new IllegalArgumentException("timeoutMillis value is negative");
    }

    复制代码
          if (nanos < 0 || nanos > 999999) {
              throw new IllegalArgumentException(
                                  "nanosecond timeout value out of range");
          }
    
          if (nanos > 0 && timeoutMillis < Long.MAX_VALUE) {
              timeoutMillis++;
          }
    
          wait(timeoutMillis);
      }
2.1.2 notify
  • notify 是 Object 类的方法,任何对象都可以调用它。notify 方法用于唤醒一个在该对象的监视器上等待的线程。

  • notify() 适用于只需要唤醒一个等待线程的情况,如果有多个线程等待,随机唤醒一个

    public final native void notify();

  • notifyAll() 唤醒对象监视器上所有等待的线程

    public final native void notifyAll();

2.2 线程通信代码实践

2.2.1 重要说明
  • 等待和通知唤醒必须放在同步代码块
    是为了确保线程在进入等待状态和被唤醒时,对共享资源的访问是受控和一致的。这种机制保证了线程间的协调和通信是安全的,避免了竞争条件和数据不一致的问题。同步块保证了在任意时刻只有一个线程 可以执行同步代码块中的代码,从而确保了线程间通信的正确性和一致性。
    举个例子,张三、李四、王五共同完成一个任务,要求张三先做,再到李四、最后王五,那么三个人就是三个线程,一个人在做任务的时候,其他人是不能动的。张三做完了,把执行权交给李四,并通知李四,李四执行完了,通知王五。
  • 调用wait ,那么当前线程也就释放了锁,即交出执行权,线程进入WAITING 状态
  • wait 和 notify 必须由同一个锁对象 调用。一个锁对象可以唤醒 由同一个锁对象调用wait 后的线程
  • wait 和 notify 是属于 Object 类 的,锁对象可以是任意对象,其他类默认都是继承了Object类的
2.2.2 代码
  • 客人来了,张三洗菜、李四切菜、王五炒菜

    public class Demo10 {
    private static final Object lock = new Object(); // 锁对象
    private static boolean vegetablesWashed = false; // 是否已经洗菜完成
    private static boolean vegetablesCut = false; // 是否已经切菜完成
    public static void main(String[] args) {
    new Thread(new Runnable() {
    @Override
    public void run() {
    synchronized (lock){
    while (!vegetablesCut){
    try {
    System.out.println("王五等待李四切菜...");
    lock.wait(); // 李四没有切完菜,继续等
    } catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    }
    }
    System.out.println("》》》》王五开始炒菜《《《《");
    try {
    Thread.sleep(1200); // 模拟炒菜过程
    System.out.println("》》》王五炒菜完成《《《");
    lock.notifyAll();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    },"王五线程").start();

    复制代码
          new Thread(new Runnable() {
              @Override
              public void run() {
                  synchronized (lock){
                      while (!vegetablesWashed){
                          try {
                              System.out.println("李四等待张三洗菜...");
                              lock.wait(); // 张三没有切完菜,继续等
                          } catch (InterruptedException e) {
                              Thread.currentThread().interrupt();
                          }
                      }
                      System.out.println("======李四开始切菜======");
                      try {
                          Thread.sleep(1600);  // 模拟切菜过程
                          vegetablesCut = true;
                          System.out.println("===李四切完菜了===");
    
                          lock.notifyAll(); // 通知王五炒菜
                      } catch (InterruptedException e) {
                          Thread.currentThread().interrupt();
                      }
                  }
              }
          },"李四线程").start();
    
          new Thread(new Runnable() {
              @Override
              public void run() {
                  synchronized (lock){
                      System.out.println("----张三开始洗菜----");
                      try {
                          Thread.sleep(1500);  // 模拟洗菜过程
    
                          vegetablesWashed = true;
                          System.out.println("---张三洗完菜了---");
                          lock.notifyAll();
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              }
          },"张三线程").start();
    
      }

    }

    王五等待李四切菜...
    李四等待张三洗菜...
    ----张三开始洗菜----
    ---张三洗完菜了---
    王五等待李四切菜...
    ======李四开始切菜======
    ===李四切完菜了===
    》》》》王五开始炒菜《《《《
    》》》王五炒菜完成《《《

分析:上一个人未完成,那么下一个人就继续等待。一个人完成了,要通知所有人,每个人根据自己的顺序,接到通知后,看是否要做,不做任务就继续等待。保证了张三先洗完菜,李四再开始切菜,李四切完菜,王五再炒菜的顺序

相关推荐
.格子衫.3 小时前
Spring Boot 原理篇
java·spring boot·后端
多云几多3 小时前
Yudao单体项目 springboot Admin安全验证开启
java·spring boot·spring·springbootadmin
Jabes.yang5 小时前
Java求职面试实战:从Spring Boot到微服务架构的技术探讨
java·数据库·spring boot·微服务·面试·消息队列·互联网大厂
聪明的笨猪猪5 小时前
Java Redis “高可用 — 主从复制”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
执尺量北斗5 小时前
[特殊字符] 基于 Qt + OpenGL 实现的入门级打砖块游戏
开发语言·qt·游戏
夏子曦6 小时前
C#内存管理深度解析:从栈堆原理到高性能编程实践
开发语言·c#
兮动人6 小时前
Spring Bean耗时分析工具
java·后端·spring·bean耗时分析工具
MESSIR226 小时前
Spring IOC(控制反转)中常用注解
java·spring
摇滚侠6 小时前
Spring Boot 3零基础教程,Demo小结,笔记04
java·spring boot·笔记
笨手笨脚の7 小时前
设计模式-迭代器模式
java·设计模式·迭代器模式·行为型设计模式