面试官最爱的面试题:wait() 和 notify() 为什么需要同步?

简介: 大家好,我是小米。今天来探讨一个常见的Java面试题:为什么线程通信的 `wait()`、`notify()` 和 `notifyAll()` 方法被定义在 Object 类里,且必须在同步方法或同步块中调用?通过小明和小红的工作场景,我们理解了这些方法的核心思想——线程间的协调与通信。它们依赖于对象锁,确保线程按预期顺序执行,避免资源争抢和死锁。掌握这些知识点,能帮助你更好地应对多线程相关的面试问题。如果你对线程同步等话题感兴趣,欢迎继续交流。



大家好,我是小米!今天来跟大家聊一个常见但很容易让人迷惑的Java面试题。面试官有时会问:为什么线程通信的 wait()、notify() 和 notifyAll() 方法被定义在 Object 类里?它们为什么必须在同步方法或同步块中被调用?

这两个问题在很多社招面试中都会出现,尤其是对于一些经验较为丰富的开发者来说,理解这些问题不仅能够帮助我们更好地掌握线程间通信,也能更深入理解Java的内存模型和多线程的精髓。接下来,让我们通过一个故事来深入探讨这些问题。

线程通信的故事:小明和小红的任务

想象一下,假设小明和小红是两个程序员,他们在同一个公司里工作。今天,公司交给了他们一个任务:他们需要交替处理一个共享资源(比如说一个文件),但是只能有一个人同时处理这个文件。于是,他们决定采用线程同步的方式来解决这个问题。

为了确保只有一个人能够处理文件,他们用到了“互斥锁”的概念:每当一个人正在处理文件时,另一个人就必须等待。于是小明和小红的工作模式如下:

  • 小明:如果他正在处理文件,他就通知小红“你可以开始了”。
  • 小红:如果她正在处理文件,她就通知小明“你可以开始了”。

为了让工作能够按计划顺利进行,小明和小红商定了一些规则。这里的“等待”和“通知”机制是非常重要的,保证了两个人的任务交替进行。

但是,问题来了!他们怎么才能保证正确的同步呢?

Java 中的 wait()、notify() 和 notifyAll() 方法

在 Java 中,线程通信就是让一个线程等待,直到其他线程通知它可以继续执行。为了解决小明和小红的问题,Java 提供了 wait()notify()notifyAll() 这三个方法。

首先,wait() 方法使当前线程进入等待状态,直到被其他线程唤醒。调用 notify() 方法会唤醒一个正在等待的线程,而 notifyAll() 会唤醒所有等待的线程。

这些方法的目的非常简单:让线程间可以协调工作,避免了资源争抢,确保了任务按照预期顺序进行。

为什么 wait()、notify() 和 notifyAll() 被定义在 Object 类中?

回到小明和小红的工作场景,如果我们要让他们的线程能“互相通知”,我们需要确保每个线程都能够“沟通”或者说共享同一个对象

在 Java 中,每个对象都可以作为“锁”来进行线程同步。而 wait()notify()notifyAll() 这些方法必须与“锁”的概念紧密结合,因此它们被定义在 Object 类中,因为每个 Java 对象都拥有这个锁,而这些方法正是利用了对象锁来进行线程通信。

想象一下,wait() 就像是小明说的“我还在处理文件,等一下”,notify() 就是小红说的“好了,可以开始了”,它们相互“通知”对方,这种通信方式需要“共用一个资源”才能协调。所以它们是作为 Object 类的一部分,这样所有对象都可以通过 wait() 和 notify() 机制来实现线程间的协作。

wait()、notify() 和 notifyAll() 为什么必须在同步方法或者同步块中被调用?

接下来,我们再来看为什么 wait()notify()notifyAll() 必须在同步方法或者同步块中被调用。

我们知道,在 Java 中,线程是通过“锁”来实现同步的。每当一个线程持有一个对象的锁时,其他线程就无法访问该对象。因此,如果一个线程需要调用 wait() 或者 notify(),它就必须持有这个对象的锁,才能保证线程的正确协调。

如果我们不在同步方法或同步块中调用这些方法,会发生什么呢?

不加锁的情况下调用 wait() 或 notify() 方法,可能会导致线程之间的协调失效,甚至可能引发程序的死锁。

小明和小红的例子也可以帮助我们理解:如果没有锁的保护,他们可能会在没有同步的情况下“互相通知”,这就像是两个没有时间顺序的人在交换信息,可能导致逻辑上的混乱。因此,Java 强制要求在同步方法或者同步块中使用 wait()、notify() 和 notifyAll() 方法,以确保线程通信在恰当的时机发生。

如何使用同步方法和同步块?

同步方法和同步块都依赖于对象的锁。我们可以通过 synchronized 关键字来标识同步代码块或方法。

1. 同步方法

同步方法是将整个方法体都加上了同步锁。比如:

在这个例子中,processFile 方法是一个同步方法,当小明或小红在处理文件时,其他线程必须等待。通过 wait() 方法让当前线程进入等待状态,直到被另一个线程调用 notify() 唤醒。

2. 同步块

同步块是将代码块加上同步锁,它可以在方法内部实现局部同步:

这里我们使用 synchronized (this) 来加锁 processFile() 方法中的代码块,这样可以更精细地控制同步,确保 wait() 和 notify() 的正确使用。

wait() 和 notify() 的执行时机和规则

了解了同步的基本概念之后,我们还需要掌握一些细节:

  • wait() 调用时,必须持有对象的锁。当调用 wait() 时,当前线程会释放锁并进入阻塞状态,直到有其他线程调用 notify() 或 notifyAll() 来唤醒它。
  • notify() 唤醒一个等待中的线程,如果有多个线程在等待,notify() 只会唤醒其中一个。notifyAll() 则会唤醒所有等待的线程。
  • 唤醒的线程会竞争重新获得锁,被唤醒的线程必须重新获得锁才能继续执行。这里的关键点在于,notify() 或 notifyAll() 只唤醒线程,但它们不会直接使线程进入执行状态,线程仍然需要等待锁的释放。

总结

通过小明和小红的工作场景,我们理解了 wait()、notify() 和 notifyAll() 这些方法背后的核心思想——线程之间的协调与通信。它们之所以被定义在 Object 类中,是因为每个对象都有锁,而线程通信是基于这些锁的。至于为什么必须在同步方法或同步块中调用它们,原因在于线程的通信需要确保在持有锁的情况下进行,否则就会导致线程的协调失败,甚至可能产生死锁等问题。

掌握了这些,你就可以轻松应对面试中的多线程相关问题啦!如果你对线程同步、线程池等其他多线程问题有兴趣,也可以随时来找我聊聊哦!

END

以上就是今天的分享,希望能帮助你在面试中脱颖而出!如果你喜欢这篇文章,记得点赞、收藏、转发,和你的朋友们一起分享吧!我们下期见!

我是小米,一个喜欢分享技术的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号软件求生,获取更多技术干货!

相关文章
|
2月前
|
Java 调度
|
6月前
|
存储 安全 Java
一天十道Java面试题----第二天(HashMap和hashTable的区别--------》sleep、wait、join)
这篇文章是关于Java面试的第二天笔记,涵盖了HashMap与HashTable的区别、ConcurrentHashMap的实现原理、IOC容器的实现方法、字节码的概念和作用、Java类加载器的类型、双亲委派模型、Java异常体系、GC如何判断对象可回收、线程的生命周期及状态,以及sleep、wait、join、yield的区别等十道面试题。
一天十道Java面试题----第二天(HashMap和hashTable的区别--------》sleep、wait、join)
【多线程面试题十】、说一说notify()、notifyAll()的区别
notify()唤醒单个等待对象锁的线程,而notifyAll()唤醒所有等待该对象锁的线程,使它们进入就绪队列竞争锁。
|
6月前
|
Go 数据库 UED
[go 面试] 同步与异步:程序执行方式的不同之处
[go 面试] 同步与异步:程序执行方式的不同之处
|
7月前
|
消息中间件 算法 Kafka
面试题Kafka问题之Kafka的副本消息同步如何解决
面试题Kafka问题之Kafka的副本消息同步如何解决
113 4
【多线程面试题九】、说一说sleep()和wait()的区别
sleep()和wait()的主要区别在于sleep()是Thread类的静态方法,可以在任何地方使用且不会释放锁;而wait()是Object类的方法,只能在同步方法或同步代码块中使用,并会释放锁直到相应线程通过notify()/notifyAll()重新获取锁。
|
6月前
|
存储 Java 调度
【多线程面试题 八】、说一说Java同步机制中的wait和notify
Java同步机制中的wait()、notify()、notifyAll()是Object类的方法,用于线程间的通信,其中wait()使当前线程释放锁并进入阻塞状态,notify()唤醒单个等待线程,notifyAll()唤醒所有等待线程。
|
6月前
|
Java 调度
搞清楚wait、sleep、join、yield四者区别,面试官直接被征服!
掌握上述多线程控制方法的运用,可以在Java多线程程序编写中进行更加深入的线程管理,确保程序运行更加高效、稳定。在面试中准确并熟练地讲解这些概念,确实有可能让面试官对你的专业能力留下深刻印象。
74 0
|
7月前
|
存储 安全 Java
Java面试题:请解释Java内存模型,并说明如何在多线程环境下使用synchronized关键字实现同步,阐述ConcurrentHashMap与HashMap的区别,以及它如何在并发环境中提高性能
Java面试题:请解释Java内存模型,并说明如何在多线程环境下使用synchronized关键字实现同步,阐述ConcurrentHashMap与HashMap的区别,以及它如何在并发环境中提高性能
62 0
|
8月前
|
Oracle Java 关系型数据库
面试知识点:notify是随机唤醒线程吗(唤醒线程顺序)?
面试知识点:notify是随机唤醒线程吗(唤醒线程顺序)?
239 0