Java线程通信之wait-notify通信方式详解

目录
  • 1.线程通信的定义
  • 2.为什么需要wait-notify?
  • 3.wait方法和notify方法
    • 1、对象的wait()方法
    • 2、对象的notify()方法
  • 4.wait方法和notify方法的原理
  • 5.wait方法和notify方法示例
    • 1、进入Object监视器的线程才能调用wait()方法
    • 2、进入Object监视器的线程才能调用notify()方法
  • 6.为什么wait和notify方法要在同步块中调用?
  • 总结

问题:

1.线程 wait()方法使用有什么前提?

2. 多线程之间如何进行通信?

3. Java 中 notify 和 notifyAll 有什么区别?

4. 为什么 wait/notify/notifyAll 这些方法不在 thread 类里面?

5. 为什么 wait 和 notify 方法要在同步块中调用?

6. notify()和 notifyAll()有什么区别?

1. 线程通信的定义

线程是操作系统调度的最小单位,有自己的栈空间,可以按照既定的代码逐步执行,但是如果每个线程间都孤立地运行,就会造资源浪费。所以在现实中,如果需要多个线程按照指定的规则共同完成一个任务,那么这些线程之间就需要互相协调,这个过程被称为线程的通信。

线程的通信可以被定义为:当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以避免无效的资源争夺。

线程间通信的方式可以有很多种:等待-通知、共享内存、管道流。“等待-通知”通信方式是Java中使用普遍的线程间通信方式,其经典的案例是“生产者-消费者”模式。

2. 为什么需要wait-notify?

场景:有几个小孩都想进入房间内使用算盘(CPU)进行计算,老王(操作系统)就使用了一把锁(synchronized)让同一时间只有一个小孩能进入房间使用算盘,于是他们排队进入房间。

(1) 小南最先获取到了锁,进入到房间内,但是由于条件不满足(没烟干不了活),小南不能继续进行计算 ,但小南如果一直占用着锁,其它人就得一直阻塞,效率太低。

(2) 于是老王单开了一间休息室(调用 wait 方法),让小南到休息室(WaitSet)等着去了,这时锁释放开, 其它人可以由老王随机安排进屋

(3) 直到小M将烟送来,大叫一声 [ 你的烟到了 ] (调用 notify 方法)

(4) 小南于是可以离开休息室,重新进入竞争锁的队列

java语言中“等待-通知”方式的线程间通信使用对象的wait()、notify()两类方法来实现。每个java对象都有wait()、notify()两类实例方法,并且wait()、notify()方法和对象的监视器是紧密相关的。

wait()、notify()两类方法在数量上不止两个。wait()、notify()两类方法不属于Thread类,而是属于java对象实例。

3. wait方法和notify方法

java对象中的wait()、notify()两类方法就如同信号开关,用于等待方和通知方之间的交互。

1、对象的wait()方法

对象的wait()方法的主要作用是让当前线程阻塞并等待被唤醒。wait()方法与对象监视器紧密相关,使用wait()方法时一定要放在同步块中。wait()方法的调用方法如下:

public class Main {
    static final Object lock = new Object();
    public static void method1() throws InterruptedException {
        synchronized( lock ) {
            lock.wait();
        }
    }
}

Object类中的wait()方法有三个版本:

(1) void wait():当前线程调用了同步对象lockwait()实例方法后,将导致当前的线程等待,当前线程进入lock的监视器WaitSet,等待被其他线程唤醒;

(2) void wait(long timeout):限时等待。导致当前的线程等待,等待被其他线程唤醒,或者指定的时间timeout用完,线程不再等待;

(3) void wait(long timeout,int nanos):高精度限时等待,其主要作用是更精确地控制等待时间。参数nanos是一个附加的纳秒级别的等待时间;

2、对象的notify()方法

对象的notify()方法的主要作用是唤醒在等待的线程。notify()方法与对象监视器紧密相关,调用notify()方法时也需要放在同步块中。notify()方法的调用方法如下:

public class Main {
    static final Object lock = new Object();
    public static void method1() throws InterruptedException {
        synchronized( lock ) {
            lock.notify();
        }
    }
}

notify()方法有两个版本:

(1)void notify()lock.notify()调用后,唤醒lock监视器等待集中的第一条等待线程;被唤醒的线程进入EntryList,其状态从WAITING变成BLOCKED。

(2) void notifyAll()lock.notifyAll()被调用后,唤醒lock监视器等待集中的全部等待线程,所有被唤醒的线程进入EntryList,线程状态从WAITING变成BLOCKED。

小结:

obj.wait():让进入Object监视器的线程到waitset等待

obj.notify():在Object上正在waitset等待的线程中挑一个唤醒

obj.notifyAll():让在Object上正在waitset等待的线程全部唤醒

4. wait方法和notify方法的原理

对象的wait()方法的核心原理大致如下:

(1) 当线程调用了lock(某个同步锁对象)的wait()方法后,jvm会将当前线程加入lock监视器的WaitSet(等待集),等待被其他线程唤醒。

(2) 当前线程会释放lock对象监视器的Owner权利,让其他线程可以抢夺lock对象的监视器。

(3) 让当前线程等待,其状态变成WAITING。在线程调用了同步对象lock的wait()方法之后,同步对象lock的监视器内部状态大致如图2-15所示。

对象的notify()或者notifyAll()​​​​​​​​​​​​​​方法的原理大致如下:

(1) 当线程调用了lock(某个同步锁对象)的notify()方法后,jvm会唤醒lock监视器WaitSet中的第一条等待线程。

(2) 当线程调用了locknotifyAll()方法后,jvm会唤醒lock监视器WaitSet中的所有等待线程。

(3) 等待线程被唤醒后,会从监视器的WaitSet移动到EntryList,线程具备了排队抢夺监视器Owner权利的资格,其状态从WAITING变成BLOCKED。

(4) EntryList中的线程抢夺到监视器的Owner权利之后,线程的状态从BLOCKED变成Runnable,具备重新执行的资格。

(1) Owner 线程发现条件不满足,调用wait 方法,即可进入WaitSet,变为 WAITING 状态 ;

(2) BLOCKED 和WAITING 的线程都处于阻塞状态,不占用CPU时间片 ;

(3) BLOCKED:线程会在Owner 线程释放锁时唤醒 ;

(4) WAITING :线程会在Owner 线程调用notify 或 notifyAll时唤醒,但唤醒后并不意味者立刻获得锁,仍需进入 EntryList 重新竞争;

5. wait方法和notify方法示例

1、进入Object监视器的线程才能调用wait()方法

小南并不能直接进入WaitSet休息室,而是获取锁进入房间后才能进入休息室,没有锁的话小南没法进入房间,更没法进入休息室。他进入休息室后就会释放锁,让其他线程竞争锁进入房间。

public class Main {
    static final Object lock = new Object();
    public static void main(String[] args) {
        synchronized (lock){
            try {
                // 只有成为了monitor对象的owner,即获得了对象锁之后,才有资格进入waitset等待
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

2、进入Object监视器的线程才能调用notify()方法

小M此时获取到了锁,进入了房间,并唤醒了在休息室中等待的小王,小M如果获取到锁进行房间时没有办法唤醒在休息室等待的小王的,因为此时小M在门外,小王根本听不到。

使用notify()唤醒等待区的一个线程:

public class Main {
    static final Object lock = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            System.out.println("t1线程开始执行...");
            synchronized (lock){
                try {
                    // 让t1线程在lock锁的waitset中等待
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 被唤醒后,继续执行
                System.out.println("线程t1被唤醒...");
            }
        },"t1");
        t1.start();
        Thread t2 = new Thread(()->{
            System.out.println("t2线程开始执行...");
            synchronized (lock){
                try {
                    // 让t2线程在lock锁的waitset中等待
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 被唤醒后,继续执行
            System.out.println("线程t2被唤醒...");
        },"t2");
        t2.start();
        Thread.sleep(2000);
        System.out.println("唤醒lock锁上等待的线程...");
        synchronized (lock){
            // 主线程拿到锁后,唤醒正在休息室中等待的某一个线程
            lock.notify();
        }
    }
}

执行结果:

t1线程开始执行...
t2线程开始执行...
唤醒lock锁上等待的线程...
线程t1被唤醒...

使用notifyAll()唤醒等待区所有的线程:

public class Main {
    static final Object lock = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            System.out.println("t1线程开始执行...");
            synchronized (lock){
                try {
                    // 让t1线程在lock锁的waitset中等待
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 被唤醒后,继续执行
                System.out.println("线程t1被唤醒...");
            }
        },"t1");

        t1.start();
        Thread t2 = new Thread(()->{
            System.out.println("t2线程开始执行...");
            synchronized (lock){
                try {
                    // 让t2线程在lock锁的waitset中等待
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 被唤醒后,继续执行
            System.out.println("线程t2被唤醒...");
        },"t2");
        t2.start();

        Thread.sleep(2000);

        System.out.println("唤醒lock锁上等待的线程...");
        synchronized (lock){
            // 主线程拿到锁后,唤醒正在休息室中等待的所有线程
            lock.notifyAll();
        }
    }
}

执行结果:

t1线程开始执行...
t2线程开始执行...
唤醒lock锁上等待的线程...
线程t2被唤醒...
线程t1被唤醒...

6. 为什么 wait 和 notify 方法要在同步块中调用?

在调用同步对象的wait()和notify()系列方法时,“当前线程”必须拥有该对象的同步锁,也就是说,wait()和notify()系列方法需要在同步块中使用,否则JVM会抛出类似如下的异常:

为什么wait和notify不在synchronized同步块的内部使用会抛出异常呢?这需要从wait()和notify()方法的原理说起。

wait()方法的原理:

首先,JVM会释放当前线程的对象锁监视器的Owner资格;其次,JVM会将当前线程移入监视器的WaitSet队列,而这些操作都和对象锁监视器是相关的。所以,wait()方法必须在synchronized同步块的内部调用。在当前线程执行wait()方法前,必须通过synchronized()方法成为对象锁的监视器的Owner。

notify()方法的原理:

JVM从对象锁的监视器的WaitSet队列移动一个线程到其EntryList队列,这些操作都与对象锁的监视器有关。所以,notify()方法也必须在synchronized同步块的内部调用。在执行notify()方法前,当前线程也必须通过synchronized()方法成为对象锁的监视器的Owner。

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • Java多线程中的wait/notify通信模式实例详解

    前言 最近在看一些JUC下的源码,更加意识到想要学好Java多线程,基础是关键,比如想要学好ReentranLock源码,就得掌握好AQS源码,而AQS源码中又有很多Java多线程经典的一些应用:再比如看了线程池的核心源码实现,又学到了很多核心实现,其实这些都可以提出来慢慢消化并变成自己的知识点,今天这个Java等待/通知模式其实是Thread.join()实现的关键,还有线程池工作线程中线程跟线程之间的通信的核心所在,故在此为了加深理解,做此记录! 参考资料<Java并发编程艺术>(电子PD

  • Java通过wait()和notifyAll()方法实现线程间通信

    本文实例为大家分享了Java实现线程间通信的具体代码,供大家参考,具体内容如下 Java代码(使用了2个内部类): package Threads; import java.util.LinkedList; /** * Created by Frank */ public class ProdCons { protected LinkedList<Object> list = new LinkedList<>(); protected int max; protected bool

  • 浅谈Java线程间通信之wait/notify

    Java中的wait/notify/notifyAll可用来实现线程间通信,是Object类的方法,这三个方法都是native方法,是平台相关的,常用来实现生产者/消费者模式.先来我们来看下相关定义: wait() :调用该方法的线程进入WATTING状态,只有等待另外线程的通知或中断才会返回,调用wait()方法后,会释放对象的锁. wait(long):超时等待最多long毫秒,如果没有通知就超时返回. notify() :通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提

  • Java多线程通信wait()和notify()代码实例

    1.wait()方法和sleep()方法: wait()方法在等待中释放锁:sleep()在等待的时候不会释放锁,抱着锁睡眠. 2.notify(): 随机唤醒一个线程,将等待队列中的一个等待线程从等待队列中移到同步队列中. 代码如下 public class Demo_Print { public static void main(String[] args) { Print p = new Print(); new Thread() { public void run() { while (

  • Java线程之间的共享与协作详解

    目录 前言 一.进程和线程 1.进程是程序运行资源分配的最小单位 2.线程是CPU 调度的最小单位,必须依赖于进程而存在 3.线程无处不在 二.CPU 核心数和线程数的关系 1.多核心 2.多线程 3.核心数.线程数 三.CPU 时间片轮转机制 四.并行和并发 1.并发 2.并行 五.高并发编程 1.CPU 资源利用的充分 2.加快用户响应时间 3.使代码模块化.异步化.简单化 六.多线程注意事项 1.线程之间的安全性 2.线程之间的死锁 3.线程多了会将服务资源耗尽形成死机.当机 七.多线程注

  • Java线程池队列PriorityBlockingQueue和SynchronousQueue详解

    目录 正文 PriorityBlockingQueue阻塞优先队列 SynchronousQueue 正文 public enum QueueTypeEnum { ARRAY_BLOCKING_QUEUE(1, "ArrayBlockingQueue"), LINKED_BLOCKING_QUEUE(2, "LinkedBlockingQueue"), DELAY_QUEUE(3, "DelayQueue"), PRIORITY_BLOCKING

  • Java线程安全和锁Synchronized知识点详解

    一.进程与线程的概念 (1)在传统的操作系统中,程序并不能独立运行,作为资源分配和独立运行的基本单位都是进程. 在未配置 OS 的系统中,程序的执行方式是顺序执行,即必须在一个程序执行完后,才允许另一个程序执行:在多道程序环境下,则允许多个程序并发执行.程序的这两种执行方式间有着显著的不同.也正是程序并发执行时的这种特征,才导致了在操作系统中引入进程的概念. 自从在 20 世纪 60 年代人们提出了进程的概念后,在 OS 中一直都是以进程作为能拥有资源和独立运行的基本单位的.直到 20 世纪 8

  • Java线程池的拒绝策略实现详解

    一.简介 jdk1.5 版本新增了JUC并发编程包,大大的简化了传统的多线程开发. Java线程池,是典型的池化思想的产物,类似的还有数据库的连接池.redis的连接池等.池化思想,就是在初始的时候去申请资源,创建一批可使用的连接,这样在使用的时候,就不必再进行创建连接信息的开销了.举个生活中鲜明的例子,在去著名洋快餐某基或者某劳的时候,配餐人员是字节从一个中间的保温箱里面直接取,然后打包就好了.不用再临时的来了一个单子,又要去拿原材料,又要去进行加工.效率明显的就是提高了很多. 既然是池子,那

  • Java线程池的分析和使用详解

    目录 1.    引言 2.线程池的使用线程池的创建 线程池的关闭 3.    线程池的分析 4.    合理的配置线程池 5.    线程池的监控 总结 1.    引言 合理利用线程池能够带来三个好处. 第一:降低资源消耗.通过重复利用已创建的线程降低线程创建和销毁造成的消耗. 第二:提高响应速度.当任务到达时,任务可以不需要的等到线程创建就能立即执行. 第三:提高线程的可管理性.线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和

  • Java四个线程常用函数超全使用详解

    目录 前言 1. wait() 2. join() 3. sleep() 4. yield() 5. 总结 5.1 wait和join的区别 5.2 wait和sleep的区别 前言 之前没怎么关注到这两个的区别以及源码探讨 后面被某个公司面试问到了,开始查漏补缺 1. wait() 使当前线程等待,直到它被唤醒,通常是通过被通知或被中断,或者直到经过一定的实时时间. 本身属于一个Object 类,查看源代码也可知:public class Object { 查看其源码可知,一共有三个重载的方法

  • 浅谈线程通信wait,notify作用

    线程通信的目的是为了能够让线程之间相互发送信号.另外,线程通信还能够使得线程等待其它线程的信号,比如,线程B可以等待线程A的信号,这个信号可以是线程A已经处理完成的信号 Wait()方法 -中断方法的执行,使本线程等待,暂时让出cpu的使用权,并允许其他线程使用这个同步方法 Notify()方法 -唤醒由于使用这个同步方而处于等待线程的某一个结束等待 Notifyall()方法 唤醒所有由于使用这个同步方法而处于等待的线程结束等待 什么时候使用wait方法 当一个线程使用的同步方法中用到某个变量

  • JAVA 线程通信相关知识汇总

    两个线程之间的通信 多线程环境下CPU会随机的在线程之间进行切换,如果想让两个线程有规律的去执行,那就需要两个线程之间进行通信,在Object类中的两个方法wait和notify可以实现通信. wait方法可以使当前线程进入到等待状态,在没有被唤醒的情况下,线程会一直保持等待状态. notify方法可以随机唤醒单个在等待状态下的线程. 来实现这样的一个功能: 让两个线程交替在控制台输出一行文字 定义一个Print类,有两个方法print1和print2,分别打印一行不同的内容 package c

  • 深入理解Java 线程通信

    当线程在系统内运行时,线程的调度具有一定的透明性,程序通常无法准确控制线程的轮换执行,但 Java 也提供了一些机制来保证线程协调运行. 传统的线程通信 假设现在系统中有两个线程,这两个线程分别代表存款者和取钱者--现在假设系统有一种特殊的要求,系统要求存款者和取钱者不断地重复存款.取钱的动作,而且要求每当存款者将钱存入指定账户后,取钱者就立即取出该笔钱.不允许存款者连续两次存钱,也不允许取钱者连续两次取钱. 为了实现这种功能,可以借助于 Object 类提供的 wait(). notify()

  • Java线程通信及线程虚假唤醒知识总结

    线程通信 线程在内部运行时,线程调度具有一定的透明性,程序通常无法控制线程的轮换执行.但Java本身提供了一些机制来保证线程协调运行. 假设目前系统中有两个线程,分别代表存款和取钱.当钱存进去,立马就取出来挪入指定账户.这涉及到线程间的协作,使用到Object类提供的wait().notify().notifyAll()三个方法,其不属于Thread类,而属于Object,而这三个方法必须由监视器对象来调用: synchronized修饰的方法,因为该类的默认实例(this)就是同步监视器,因此

随机推荐