『Java练习生的自我修养』java-se进阶³ • 线程的等待与唤醒

『Java练习生的自我修养』java-se进阶³ • 线程的等待与唤醒_第1张图片

☕☕ Java进阶攻坚克难,持续更新,一网打尽IO、注解、多线程…等java-se进阶内容。


前言:

如果看过前面两篇文章,相信大家已经掌握了线程的创建方式和线程同步问题,现在让我们探索一下多线程的最后一个板块:线程之间的通信。

传送门:

  • 『Java练习生的自我修养』java-se进阶¹ • 初识多线程
  • 『Java练习生的自我修养』java-se进阶² • 并发与多线程

csdn

线程之间的通信

假设现在有两个线程,一个线程负责向数据容器中生产数据,另一个线程需要从数据容器中取出数据使用,那么只有在生产数据的线程把数据放进容器之后,取数据的线程才能去容器中取数据,不然就是取了个寂寞,这就需要生产数据的线程在将数据放进容器后通知另一个线程。

『Java练习生的自我修养』java-se进阶³ • 线程的等待与唤醒_第2张图片

这个例子就是典型的生产者与消费者问题,学过《操作系统》的同学是不是突然间通透了,当多个线程并发执行或者交替工作时,除了线程间的同步,必须还要有线程间的通信机制保证程序的正常运行。


线程的等待与唤醒

线程间进行通信的方式之一就是使用线程的等待与唤醒方法,这两个方法在Object类中。

  • void wait():在其他线程调用此对象的notify()方法或者notifyAll()方法前,导致当前线程等待。
  • void notify():唤醒在此对象监视器上等待的单个线程,会继续执行wait()方法之后的代码。

回顾一下前面两篇文章中都提到过的线程的状态,Thread类中的内部类Thread.State中定义了线程的状态,前文提到的就不再赘述,这里需要我们注意的是线程从运行态切换到非运行的状态主要有三种情况:

  1. 第一种就是多个线程争夺锁对象时,没有得到锁的线程会被阻塞。

『Java练习生的自我修养』java-se进阶³ • 线程的等待与唤醒_第3张图片

  1. 第二种情况时我们调用了sleep()方法,线程进入了睡眠状态。sleep()与锁无关,而且需要补充的一点是sleep()方法中指定的时间是线程不会运行的最短时间,sleep()方法不能保证该线程睡眠到期后就立刻开始执行。

『Java练习生的自我修养』java-se进阶³ • 线程的等待与唤醒_第4张图片

  1. 最后一种让线程进入到非运行的情况,也是本文的重点,就是调用了wait()方法。

『Java练习生的自我修养』java-se进阶³ • 线程的等待与唤醒_第5张图片


线程间通信案例

线程的等待与唤醒机制可以实现线程间的通信,说了半天可能还是一头雾水,线程间怎么就通信了?下面我们来尝试实现一个买奶茶案例

『Java练习生的自我修养』java-se进阶³ • 线程的等待与唤醒_第6张图片

顾客要买奶茶,和店员说“我要喝珍珠奶茶”,顾客就等着店员做奶茶(即调用wait方法),当店员做好奶茶以后通知顾客(即调用notify方法),然后顾客开始喝奶茶。

下面我们用代码实现一下:

public class BuyMilkyTea {

    public static void main(String[] args) {
//        创建锁对象,保证唯一
        Object o = new Object();
//        创建顾客线程
        new Thread(() -> {
            synchronized (o) {
                System.out.println("顾客:我要喝珍珠奶茶");
                try {
//                    wait()方法要作用于锁对象上
                    o.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
//                奶茶做好开始喝
                System.out.println("顾客:喝奶茶ing,吸吸吸!");
            }
        }).start();
//        创建店员线程
        new Thread(() -> {
            synchronized (o) {
                System.out.println("店员:做奶茶ing...");
//                设置一个延迟
                for (int i = 0; i < 5; i++) {
                    try {
                        Thread.sleep(1000);
                        System.out.print(i + "s ");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        System.out.println("...");
                    }
                }
//                通知顾客奶茶做好了
                System.out.println("店员:奶茶做好了~");
//                唤醒顾客线程
                o.notify();
            }
        }).start();
    }
}

⭐运行结果:

『Java练习生的自我修养』java-se进阶³ • 线程的等待与唤醒_第7张图片


带参wait()notifyAll()方法

如果动手敲过代码的话,可能你会注意到Object对象还有两个与线程有关的方法,wait(long time)notifyAll(),这里做一个补充。

  • wait(long time):同sleep()方法类似,可以设置一个毫秒值,线程如果在毫秒值结束后,还没有被notify()唤醒,线程会自动醒来,进入到Runnable/Blocked状态。
  • notifyAll()notify()方法用于唤醒单个线程,而notifyAll()用于唤醒在此对象监视器上等待的所有线程。
下篇预告:IO流读写文件


创作不易,如果觉得本文对你有所帮助,欢迎点赞关注收藏。‍♀️

@作者:Mymel_晗,计算机专业练习时长两年半的Java练习生~‍♂️

文末已至,咱们下篇再见

┊少年听雨歌楼上,红烛昏罗帐。┊
-虞美人·听雨-

『Java练习生的自我修养』java-se进阶³ • 线程的等待与唤醒_第8张图片

你可能感兴趣的:(Java进阶指北,java,多线程,线程间通信)