摘要:大家好,我是小米!今天来跟大家聊一个常见但很容易让人迷惑的Java面试题。面试官有时会问:为什么线程通信的 wait、notify 和 notifyAll 方法被定义在 Object 类里?它们为什么必须在同步方法或同步块中被调用?
大家好,我是小米!今天来跟大家聊一个常见但很容易让人迷惑的Java面试题。面试官有时会问:为什么线程通信的 wait、notify 和 notifyAll 方法被定义在 Object 类里?它们为什么必须在同步方法或同步块中被调用?
这两个问题在很多社招面试中都会出现,尤其是对于一些经验较为丰富的开发者来说,理解这些问题不仅能够帮助我们更好地掌握线程间通信,也能更深入理解Java的内存模型和多线程的精髓。接下来,让我们通过一个故事来深入探讨这些问题。
想象一下,假设小明和小红是两个程序员,他们在同一个公司里工作。今天,公司交给了他们一个任务:他们需要交替处理一个共享资源(比如说一个文件),但是只能有一个人同时处理这个文件。于是,他们决定采用线程同步的方式来解决这个问题。
为了确保只有一个人能够处理文件,他们用到了“互斥锁”的概念:每当一个人正在处理文件时,另一个人就必须等待。于是小明和小红的工作模式如下:
小明:如果他正在处理文件,他就通知小红“你可以开始了”。小红:如果她正在处理文件,她就通知小明“你可以开始了”。为了让工作能够按计划顺利进行,小明和小红商定了一些规则。这里的“等待”和“通知”机制是非常重要的,保证了两个人的任务交替进行。
但是,问题来了!他们怎么才能保证正确的同步呢?
在 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 调用时,必须持有对象的锁。当调用 wait 时,当前线程会释放锁并进入阻塞状态,直到有其他线程调用 notify 或 notifyAll 来唤醒它。notify 唤醒一个等待中的线程,如果有多个线程在等待,notify 只会唤醒其中一个。notifyAll 则会唤醒所有等待的线程。唤醒的线程会竞争重新获得锁,被唤醒的线程必须重新获得锁才能继续执行。这里的关键点在于,notify 或 notifyAll 只唤醒线程,但它们不会直接使线程进入执行状态,线程仍然需要等待锁的释放。通过小明和小红的工作场景,我们理解了 wait、notify 和 notifyAll 这些方法背后的核心思想——线程之间的协调与通信。它们之所以被定义在 Object 类中,是因为每个对象都有锁,而线程通信是基于这些锁的。至于为什么必须在同步方法或同步块中调用它们,原因在于线程的通信需要确保在持有锁的情况下进行,否则就会导致线程的协调失败,甚至可能产生死锁等问题。
掌握了这些,你就可以轻松应对面试中的多线程相关问题啦!如果你对线程同步、线程池等其他多线程问题有兴趣,也可以随时来找我聊聊哦!
以上就是今天的分享,希望能帮助你在面试中脱颖而出!如果你喜欢这篇文章,记得点赞、收藏、转发,和你的朋友们一起分享吧!我们下期见!
来源:泽洋教育