java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java的wait与notify

Java多线程中的wait与notify方法详解

作者:一只爱打拳的程序猿

这篇文章主要介绍了Java多线程中的wait与notify方法详解,线程的调度是无序的,但有些情况要求线程的执行是有序的,因此,我们可以使用 wait() 方法来使线程执行有序,需要的朋友可以参考下

 前言

我们知道,线程的调度是无序的,但有些情况要求线程的执行是有序的。

因此,我们可以使用 wait() 方法来使线程执行有序。

本期讲解 Java 多线程中 synchronized 锁配套使用的 wait 方法、notify方法和notifyAll方法,以及 wait 方法与 sleep 方法之间的区别、为什么要使用 wait 和 notify 方法。

为什么要使用wait()方法和notify()方法?

当我们的 Java 代码使用 synchronized 进行加锁时,会出现线程之间抢占资源的情况。

这样就会导致某一个线程不符合条件却反复抢到资源,其他线程参与不了资源。

因此得使用 wait() 方法与 notify() 方法来解决该问题。

通过现实生活中的经历举一例子:

把三个线程比做人,把一台 ATM 机比作锁(synchronized)。

当这三个线程去取钱时,线程1优先进入了 ATM 机里面取钱。

当 ATM 里面没有钱时,线程1就出了 ATM 机。但由于线程离开了 ATM 机后,会一直与线程2和线程3抢占 ATM 机,因此会造成一个极端的后果,就是线程1一直进入 ATM 机然后出 ATM 机,并且一直循环下去。

以上例子,线程1发现 ATM 没钱可取,却还是反复进出 ATM 这样这样其他线程就无法尝试取钱,对应的就是多线程中的多个线程竞争锁(synchroized)的情况,如何解决以上问题。

使用 wait 方法和 notify 方法。当 ATM(synchronized) 内使用了 wait 方法,线程1取不了钱就会取消锁状态并且处于等待状态,当其他线程进入 ATM 机并且取到了钱这时候就可以使用 notify 方法唤醒 线程1的等待状态,那么线程1又可以进行取钱操作,也就是进行锁的竞争。

在使用 wait 方法后,线程1发现 ATM 里面没有钱可取,就会通过 wait 方法来释放锁并且进行阻塞等待(也就是暂时不参与 CPU 的调度、锁的竞争),这个时候线程2和线程3就能很好的参与取钱这个操作了。

当其他线程 使用 notify 方法时,发现 ATM 里面又有钱可取了。因此就会唤醒线程1的阻塞等待,这时线程1又可以参与 ATM(锁) 的竞争。直到,所有的线程都取到钱为止。

那么使得上述三个线程能供协调的完成取钱这个工作,会用到三个方法:

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

1. wait()方法

wait 方法使用后:会把当前的执行的线程进行等待阻塞,然后释放当前线程的锁状态,当满足了一定条件后就被唤醒,重新尝试获取这个锁。

wait 结束条件的为:

解释:interrupt(),在一个线程中调用另一个线程的interrupt()方法,即会向那个线程发出信号—线程中断状态已被设置。至于那个线程何去何从,由具体的代码实现决定。

wait 和 notify 方法是 Object 类里面的方法,只要是一个类对象都能调用这两个方法。因此,我们可以写出以下代码:

    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        System.out.println("Hello object");
        object.wait();
        System.out.println("object结束");
    }

运行后打印:

以上代码运行后打印出一个非法的警告:非法的锁状态异常,因为 wait 方法必须要搭配 synchronized 来使用,脱离了 synchronized 的前提下 使用 wait 就会出现报错。

2. notify()方法

notify 方法是唤醒等待的线程,也就是唤醒调用了 wait 方法的线程。

notify 方法作用:

在理解 wait 方法和 notify 方法的作用以及使用方法后,下面我们来看下 wait 方法和 notify 方法的结合使用。 

3. wait()和notify()方法的使用

代码案例:使用 notify() 方法唤醒 thread1线程。

因此,有以下代码:

    public static void main(String[] args) {
        Object object = new Object();
        Thread thread1 = new Thread(()-> {
            synchronized (object) {
                System.out.println("thread1开始");
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("thread1结束");
            }
        });
        thread1.start();//启动thread1线程
        Thread thread2 = new Thread(()-> {
            synchronized (object) {
                System.out.println("thread2开始");
                object.notify();
                System.out.println("thread2结束");
            }
        });
        thread2.start();//启动thread2线程
    }

运行后打印:

以上代码,输出顺序与需求有所差异,但最终还是达到了效果。

造成输出顺序的不规则原因为:

当 thread1 线程被 wait 前打印了语句“thread1开始”,thread2 线程 中调用了 notify 方法,这时会唤醒 thread1 线程,但是前提得执行完 thread2 中的内容“thread2开始”、“thread2结束”这两个条语句。随后才输出被唤醒的 thread1 线程中的“thread1结束”语句。

当然,既然这样为啥我们不使用 join() 方法呢,thread1 线程完全执行完毕,再执行 thread2线程呢?具体情况具体分析,当我们的代码需求满足使用 join() 方法时,我们就使用 join() 方法。

对应上述代码,join() 方法会使 thread1 线程执行完毕后再执行 thread2 线程。而 wait() 和 notify() 方法会使 thread1 线程执行一部分后,执行 thread2 线程,执行完 thread2 一部分代码后,再执行thread1 线程。这样就满足了特定的条件,类似于上文中线程取钱情况。大家可以自行尝试一番。

注意,wait() 方法的初心就是为了等待、阻塞的效果。在 synchronized 内调用 wait() 方法,得按 Alt+Enter 这两个组合键来 try/catch 异常。

4. notifyAll()方法

notifyAll() 方法是用来唤醒当前对象的所有调用 wait() 的线程。案例:

因此,前两个线程都通过 Object 类的引用调用了 wait() 方法造成阻塞,最后一个线程调用 notifyAll() 则唤醒了所有调用 wait() 方法的线程,如以下代码:

public static void main(String[] args) {
        Object object = new Object();//实例化一个Object类的对象
        Thread thread1 = new Thread(()->{
            synchronized (object) {
                System.out.println("thread1-开始");
                try {
                    object.wait();//thread1中调用wait方法
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("thread1-结束");
            }
        });//创建thread1线程
        thread1.start();//启动thread1线程
        Thread thread2 = new Thread(()->{
            synchronized(object) {
                System.out.println("thread2-开始");
                try {
                    object.wait();//thread2调用wait方法
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("thread2-结束");
            }
        });//创建thread2线程
        thread2.start();//启动thread2线程
        Thread thread3 = new Thread(()->{
            synchronized (object) {
                object.notifyAll();//thread3中调用notifyAll方法
                System.out.println("thread3调用了notifyAll方法");
            }
        });//创建thread3线程
        thread3.start();//启动thread3线程
    }

运行后打印:

以上代码,通过 notifyAll() 方法唤醒了所有等待的线程。如果我把 notifyAll() 方法替换为 notify() 方法,此时就会随机唤醒一个正在等待的线程。如以下打印结果:

通过上面截图,我们可以观察到随机唤醒的是 thread1 线程。 

5. wait()和sleep()的区别

wait 与 sleep 之间的区别:

案例:

有两线程,main 线程与 thread 线程,main 线程内包含 thread 线程,main 线程内有“Hello main”语句, thread 线程内有“Hello thread”语句。

在 main 线程内创建一个 thread 线程,并且在 thread 线程内使用 Object 类对象调用带参的 wait() 方法,并设置参数 为2000。

在main 方法内使用 Object 类对象调用 notify() 唤醒 thread 线程。使得输出 main 线程内语句后停顿两秒输出 thread 线程内语句。

有以下代码:

public static void main(String[] args) {
        Object object = new Object();//实例化一个Object类对象
        Thread thread = new Thread(()->{
            synchronized (object) {
                try {
                    object.wait(2000);//thread调用了带参wait方法,停顿了两秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Hello thread");
            }
        });//创建thread线程
        thread.start();//启动thread线程
        synchronized (object) {
            object.notify();//main方法内调用notify方法
        }
        System.out.println("Hello main");
    }

运行后打印:

输出“Hello main”语句后停顿了两秒,输出“Hello thread”线程。

重点: 

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

您可能感兴趣的文章:
阅读全文