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

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

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

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

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

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

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

        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,具备重新执行的资格。

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

        (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休息室,而是获取锁进入房间后才能进入休息室,没有锁的话小南没法进入房间,更没法进入休息室。他进入休息室后就会释放锁,让其他线程竞争锁进入房间。

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

        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在门外,小王根本听不到。

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

        使用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会抛出类似如下的异常:

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

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

        wait()方法的原理:

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

        notify()方法的原理:

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

        总结

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

        声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。