Java多线程中wait notify等待唤醒机制详解

 更新时间:2024年10月03日 10:35:06   作者:IYF.星辰  
这篇文章主要介绍了Java多线程中wait notify等待唤醒机制,由于线程之间是抢占式执行的,因此线程的执行顺序难以预知,但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序,所以这里我们来介绍下等待唤醒机制,需要的朋友可以参考下

一.等待唤醒机制简介

由于线程之间是抢占式执行的,因此线程的执行顺序难以预知。但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序。为了完成协调的工作,这里主要设计三个方法:

  • wait() / wait(long timeout) : 让当前线程进入等待状态
  • notify() / notifyAll(): 唤醒在当前对象上等待的线程

注意:wait,notify,notifyAll都是Object类的方法

二.synchronized与wait()与notify()

  • synchronized的含义:

Java中每一个对象都可以成为一个监视器(Monitor), 该Monitor由一个锁(lock), 一个等待队列(waiting queue ), 一个入口队列( entry queue).

对于一个对象的方法, 如果没有synchronized关键字, 该方法可以被任意数量的线程,在任意时刻调用。

对于添加了synchronized关键字的方法,任意时刻只能被唯一的一个获得了对象实例锁的线程调用。

synchronized用于实现多线程的同步操作

  • wait()功能:

wait(), notify(), notifyAll() 和 synchonized 需要搭配使用, 用于线程同步

wait()总是在一个循;环中被调用,挂起当前线程来等待一个条件的成立。 Wait调用会一直等到其他线程调用notifyAll()时才返回。

当一个线程在执行synchronized 的方法内部,调用了wait()后, 该线程会释放该对象的锁, 然后该线程会被添加到该对象的等待队列中(waiting queue), 只要该线程在等待队列中, 就会一直处于闲置状态, 不会被调度执行。 要注意wait()方法会强迫线程先进行释放锁操作,所以在调用wait()时, 该线程必须已经获得锁,否则会抛出异常。由于wait()在synchonized的方法内部被执行, 锁一定已经获得, 就不会抛出异常了。

  • notify()的功能:

wait(), notify(), notifyAll() 和 synchonized 需要搭配使用, 用于线程同步

当一个线程调用一个对象的notify()方法时, 调度器会从所有处于该对象等待队列(waiting queue)的线程中取出任意一个线程, 将其添加到入口队列( entry queue) 中. 然后在入口队列中的多个线程就会竞争对象的锁, 得到锁的线程就可以继续执行。 如果等待队列中(waiting queue)没有线程, notify()方法不会产生任何作用

notifyAll() 和notify()工作机制一样, 区别在于notifyAll()会将等待队列(waiting queue)中所有的线程都添加到入口队列中(entry queue)

三.等待唤醒机制案例

1.让t1执行wait()方法。

2.此时t2得到锁,再让t2执行notify()方法释放锁。

3.此时t1得到锁,t1会自动从wait()方法之后的代码,继续执行。

4.通过上述流程,我们就可以清楚的看到,wait()和notify()各自是怎么工作的了,也可以知道两者是怎么配合的了。

import java.util.*;
public class Main {
    //创建一个将被两个线程同时访问的共享对象
    public static Object loker = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            synchronized (loker){
                System.out.println("线程一初次获得对象锁,执行过程中调用锁对象的wait()方法~~");
                try {
                    loker.wait();
                    System.out.println("当线程一被唤醒后,后面的代码继续执行");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程一运行结束");
        },"线程一");
        Thread t2 = new Thread(()->{
            synchronized (loker){
                System.out.println("线程二初次获得对象锁,执行过程中调用锁对象的notify()方法~~");
                loker.notify();
                System.out.println("唤醒线程一前,后面的代码继续执行~~");
            }
            System.out.println("线程二结束");
        },"线程二");
        t1.start();
        //防止t2优先获得CPU执行权而错过唤醒t1
        Thread.sleep(1000);
        t2.start();
    }
}

运行结果(运行流程也就是运行的打印结果):

例题一

有三个线程,线程名称分别为:a,b,c。每个线程打印自己的名称。

需要让他们同时启动,并按 c,b,a的顺序打印

代码详解:

import java.util.*;
public class Test {
    public static Object loker1 = new Object();
    public static Object loker2 = new Object();
    public static void main(String[] args) {
        System.out.println("打印顺序如下:");
        Thread t1 = new Thread(()->{
            //为了防止线程A的唤醒没有被线程B接受,这里先让线程A睡一会
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+": C");
            synchronized (loker1){
                loker1.notify();
            }
        },"线程C");
        Thread t2 = new Thread(()->{
            synchronized (loker1){
                //等待线程A的唤醒
                try {
                    loker1.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+": B");
            //线程B唤醒线程C
            synchronized (loker2){
                loker2.notify();
            }
        },"线程B");
        Thread t3 = new Thread(()->{
            //线程C等待线程A的唤醒
            synchronized (loker2){
                try {
                    loker2.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+": A");
        },"线程A");
        //开启线程
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果:

例题二

有三个线程,分别只能打印A,B和C

要求按顺序打印ABC,打印10次

输出示例:

ABC

ABC

ABC

ABC

ABC

ABC

ABC

ABC

ABC

ABC

大致过程:

代码详解:

import java.util.*;
public class Demo {
    private static Object locker1 = new Object();
    private static Object locker2 = new Object();
    private static Object locker3 = new Object();
    public static void main(String[] args) throws InterruptedException {
        System.out.println("打印结果:");
        Thread t1 = new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    synchronized (locker1) {
                        locker1.wait();
                    }
                    System.out.print("A");
                    synchronized (locker2) {
                        locker2.notify();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread t2 = new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    synchronized (locker2) {
                        locker2.wait();
                    }
                    System.out.print("B");
                    synchronized (locker3) {
                        locker3.notify();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread t3 = new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    synchronized (locker3) {
                        locker3.wait();
                    }
                    System.out.println("C");
                    synchronized (locker1) {
                        locker1.notify();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t1.start();
        t2.start();
        t3.start();
        //让三个线程都拿到锁
        Thread.sleep(1000);
        // 从线程 t1 启动
        synchronized (locker1) {
            locker1.notify();
        }
    }
}

运行结果:

四.什么时候释放锁——wait()与notify()

由于等待一个锁定线程只有在获得这把锁之后,才能恢复运行,所以让持有锁的线程在不需要锁的时候及时释放锁是很重要的。在以下情况下,持有锁的线程会释放锁:

1.执行完同步代码块。

2.在执行同步代码块的过程中,遇到异常而导致线程终止。

3.在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放锁,进行对象的等待池。

除了以上情况外,只要持有锁的对象还没有执行完同步代码块,就不会释放锁。因此在以下情况下,线程不会释放锁:

1.在执行同步代码块的过程中,执行了Thread.sleep()方法,当前线程放弃CPU,开始睡眠,在睡眠中不会释放锁。

2.在执行同步代码块的过程中,执行了Thread.yield()方法,当前线程放弃CPU,但不会释放锁。

3.在执行同步代码块的过程中,其他线程执行了当前对象的suspend()方法,当前线程被暂停,但不会释放锁。但Thread类的suspend()方法已经被废弃。

到此这篇关于Java多线程中wait notify等待唤醒机制详解的文章就介绍到这了,更多相关Java等待唤醒机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot配置文件properties和yml的实现

    SpringBoot配置文件properties和yml的实现

    本文主要介绍了SpringBoot配置文件properties和yml的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-04-04
  • Java实现桥接方法isBridge()和合成方法isSynthetic()

    Java实现桥接方法isBridge()和合成方法isSynthetic()

    本文主要介绍了Java实现桥接方法isBridge()和合成方法isSynthetic(),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • JAVA生成pdf文件的实操教程

    JAVA生成pdf文件的实操教程

    PDF是可移植文档格式,是一种电子文件格式,具有许多其他电子文档格式无法相比的优点,下面这篇文章主要给大家介绍了关于JAVA生成pdf文件的相关资料,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2022-11-11
  • default怎么修饰接口中的方法详解

    default怎么修饰接口中的方法详解

    今天给各位小伙伴们总结一下default怎么修饰接口中的方法,文中有非常详细的图文解说.对正在学习java的小伙伴们很有帮助,需要的朋友可以参考下
    2021-05-05
  • Spring bean不被GC的真正原因及分析

    Spring bean不被GC的真正原因及分析

    这篇文章主要介绍了Spring bean不被GC的真正原因及分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-04-04
  • java 使用ConcurrentHashMap和计数器实现锁

    java 使用ConcurrentHashMap和计数器实现锁

    这篇文章主要介绍了java 使用ConcurrentHashMap和计数器实现锁的相关资料,需要的朋友可以参考下
    2017-05-05
  • SpringBoot项目使用MDC给日志增加唯一标识的实现步骤

    SpringBoot项目使用MDC给日志增加唯一标识的实现步骤

    本文介绍了如何在SpringBoot项目中使用MDC(Mapped Diagnostic Context)为日志增加唯一标识,以便于日志追踪,通过创建日志拦截器、配置拦截器以及修改日志配置文件,可以实现这一功能,文章还提供了源码地址,方便读者学习和参考,感兴趣的朋友一起看看吧
    2025-03-03
  • JavaFX Application应用实例

    JavaFX Application应用实例

    下面小编就为大家带来一篇JavaFX Application应用实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-10-10
  • Java的wait(), notify()和notifyAll()使用心得

    Java的wait(), notify()和notifyAll()使用心得

    本篇文章是对java的 wait(),notify(),notifyAll()进行了详细的分析介绍,需要的朋友参考下
    2013-08-08
  • 详解java中的PropertyChangeSupport与PropertyChangeListener

    详解java中的PropertyChangeSupport与PropertyChangeListener

    这篇文章主要介绍了详解java中的PropertyChangeSupport与PropertyChangeListener的相关资料,需要的朋友可以参考下
    2017-09-09

最新评论