Java 多线程系列Ⅲ(wait+notify+notifyAll)

简介: Java 多线程系列Ⅲ(wait+notify+notifyAll)

一、初识 wait、notify、notifyAll

我们知道由于线程的抢占式执行导致线程之间的调度是随机的,无序的。但是在一些场景下我们有需要合理的协调多个线程的执行顺序。我们知道使用 join 可以控制线程执行顺序,但是 join 只能让一个线程执行完在执行另外一个线程,功能有限。因此我们引入了 wait 和 notify/notifyAll 这样一组API用来更灵活地控制线程执行的顺序。举个例子:

针对上述情况,我们可以使用 wait/notify 有效解决:

假如张三发现ATM没钱,就(wait)释放锁走出ATM机,并进行阻塞等待(暂时不参与CPU调度,不参与锁竞争),直到有人向ATM机存钱后,此时时机成熟,就可以唤醒(notify)张三,继续参与ATM机锁竞争。

二、wait、notify、notifyAll 功能介绍

1、wait()

wait():发现条件不满足/时机不成熟,就调用 wait() 阻塞等待,同时释放当前锁,当满足一定条件时可以被唤醒,重新尝试获取这个锁。

:使用 wait() 阻塞等待会让线程进入WAITING状态

wait等待结束条件:

  1. 其他线程调用该对象的 notify 方法.
  2. wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间).
  3. 其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常.

2、notify()

notify():其他线程构造了一个成熟的条件,就可以调用 notify() 唤醒它。

notify注意事项:

  1. 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
  2. 如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 “先来后到”)
  3. 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行 完,也就是退出同步代码块之后才会释放对象锁。

3、notifyAll()

notifyAll():notify() 只是唤醒某一个等待线程。使用 notifyAll() 可以一次唤醒所有的等待同一对象的线程。

notifyAll注意事项:

虽然是同时唤醒 多个线程, 但是这 多个线程需要竞争锁,所以并不是同时执行,而仍然是先获取锁的先执行。

4、wait、notify、notifyAll 要点总结

wait/notify/notifyAll 都是Object 的方法,因此只要是个类对象都可以使用 wait、notify、notifyAll。

wait/notify/notifyAll 要搭配 synchronized 来使用. 脱离 synchronized 使用会直接抛出异常:IllegalMonitorStateException

synchronized 的锁对象必须和调用 wait/notify/notifyAll 方法的对象是同一个。

针对某一加锁对象,先执行wait 然后 notify/notifyAll,此时才有效果,如果没有 wait 就 notify 相当于“一炮打空”,此时 wait 无法唤醒,不过代码不会出现其他异常。

5、wait/notify 使用示例

public class ThreadExample_wait_notify {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        Thread t1 = new Thread(()->{
           synchronized (object) {
               try {
                   System.out.println("wait开始");
                   object.wait();
                   System.out.println("wait结束");
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });

        Thread t2 = new Thread(()->{
           synchronized (object) {
               System.out.println("notify开始");
               object.notify();
               System.out.println("notify结束");
           }
        });
        // 等待t1中wait解锁阻塞,否则t2中的notify没有实质效果
        t1.start();
        Thread.sleep(1000);
        t2.start();
    }
}

结果说明:

上述代码 t1 先获取到锁,执行到 wait 时 t1阻塞解锁,1秒后,t2获得锁,执行到 notify 时 t1 线程就会被唤醒,但是notify后不会立即释放锁,当 t2 代码块中的逻辑执行完后才释放锁,t1 线程才能获取锁继续向下执行。

三、wait、join、sleep 归纳(1)wait

wait()方法是 Object类 提供的实例方法,可以使线程进入等待状态,直到其他线程调用了该对象的notify()或notifyAll()方法。通常被用于线程间通信,如生产者-消费者模式中(后续介绍),消费者需要等待生产者通知有新数据可取。当线程调用 wait() 方法时,它会释放占据的锁,并且线程的状态为WAITING,直到notify()或notifyAll()方法被调用。


(2)join

join() 方法是 Thread类 提供的方法静态方法,用于等待被调用线程执行完毕。在主线程中调用了子线程的join() 方法后,主线程会进入 WAITING 状态,直到子线程执行完毕才会继续执行。可以用来保证多个线程按照指定顺序执行。


(3)sleep

sleep()方法也是 Thread类 提供的实例方法,它可以使当前线程暂停执行一段时间。当线程调用 sleep() 方法时,它不会释放锁,线程的状态为 TIMED_WAITING 。通常被用于控制程序执行的速度或时间,或常常在循环内部以等待某些条件的发生。


总的来说,wait() 方法是用于线程间的通信,join() 方法是用于等待其他线程执行完毕,sleep()方法是用于暂停当前线程的执行。在使用上, wait 需要搭配 synchronized 使用,sleep 和 join 则不需要。总之本质上没有可比性,唯一的相同点就是都可以让线程放弃执行一段时间。它们的使用 场景 不同,需要根据实际需求选择合适的方法。


相关文章
|
6月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
268 0
|
7月前
|
Java 数据挖掘 调度
Java 多线程创建零基础入门新手指南:从零开始全面学习多线程创建方法
本文从零基础角度出发,深入浅出地讲解Java多线程的创建方式。内容涵盖继承`Thread`类、实现`Runnable`接口、使用`Callable`和`Future`接口以及线程池的创建与管理等核心知识点。通过代码示例与应用场景分析,帮助读者理解每种方式的特点及适用场景,理论结合实践,轻松掌握Java多线程编程 essentials。
457 5
|
7月前
|
监控 搜索推荐 Java
Java 多线程最新实操技术与应用场景全解析:从基础到进阶
本文深入探讨了Java多线程的现代并发编程技术,涵盖Java 8+新特性,如CompletableFuture异步处理、Stream并行流操作,以及Reactive编程中的Reactor框架。通过具体代码示例,讲解了异步任务组合、并行流优化及响应式编程的核心概念(Flux与Mono)。同时对比了同步、CompletableFuture和Reactor三种实现方式的性能,并总结了最佳实践,帮助开发者构建高效、扩展性强的应用。资源地址:[点击下载](https://pan.quark.cn/s/14fcf913bae6)。
444 3
|
8月前
|
算法 Java 调度
Java多线程基础
本文主要讲解多线程相关知识,分为两部分。第一部分涵盖多线程概念(并发与并行、进程与线程)、Java程序运行原理(JVM启动多线程特性)、实现多线程的两种方式(继承Thread类与实现Runnable接口)及其区别。第二部分涉及线程同步(同步锁的应用场景与代码示例)及线程间通信(wait()与notify()方法的使用)。通过多个Demo代码实例,深入浅出地解析多线程的核心知识点,帮助读者掌握其实现与应用技巧。
149 1
|
8月前
|
Java
java 多线程异常处理
本文介绍了Java中ThreadGroup的异常处理机制,重点讲解UncaughtExceptionHandler的使用。通过示例代码展示了当线程的run()方法抛出未捕获异常时,JVM如何依次查找并调用线程的异常处理器、线程组的uncaughtException方法或默认异常处理器。文章还提供了具体代码和输出结果,帮助理解不同处理器的优先级与执行逻辑。
195 1
|
3月前
|
Java
如何在Java中进行多线程编程
Java多线程编程常用方式包括:继承Thread类、实现Runnable接口、Callable接口(可返回结果)及使用线程池。推荐线程池以提升性能,避免频繁创建线程。结合同步与通信机制,可有效管理并发任务。
186 6
|
6月前
|
Java API 微服务
为什么虚拟线程将改变Java并发编程?
为什么虚拟线程将改变Java并发编程?
322 83
|
3月前
|
Java 调度 数据库
Python threading模块:多线程编程的实战指南
本文深入讲解Python多线程编程,涵盖threading模块的核心用法:线程创建、生命周期、同步机制(锁、信号量、条件变量)、线程通信(队列)、守护线程与线程池应用。结合实战案例,如多线程下载器,帮助开发者提升程序并发性能,适用于I/O密集型任务处理。
335 0
|
8月前
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
293 0
|
4月前
|
算法 Java
Java多线程编程:实现线程间数据共享机制
以上就是Java中几种主要处理多线程序列化资源以及协调各自独立运行但需相互配合以完成任务threads 的技术手段与策略。正确应用上述技术将大大增强你程序稳定性与效率同时也降低bug出现率因此深刻理解每项技术背后理论至关重要.
300 16