为什么wait/notify方法要写在synchronized代码块中?

本文详细解释了Java中wait/notify方法为何必须在synchronized代码块中使用的原因。synchronized用于保证多线程同步,wait()使线程释放锁并等待,notify()唤醒等待队列中的线程。如果不使用synchronized,可能会导致非法状态异常,并影响线程间通信的正确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为什么wait/notify方法要配合synchronized使用?

synchronized含义:

       Java中每一个对象都可以成为一个监视器(Monitor), 该Monitor由一个锁(lock), 一个等待队列(waiting queue), 一个入口队列(entry queue).

       对于一个对象的方法, 如果没有synchronized关键字, 该方法可以被任意数量的线程,在任意时刻调用。

       对于添加了synchronized关键字的方法,任意时刻只能被唯一的一个获得了对象实例锁的线程调用。

       synchronized用于实现多线程的同步操作

synchronized的三种应用方式,Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:

  • 普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
  • 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
  • 同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
  • 对于普通同步方法,锁是当前实例对象。
  • 对于静态同步方法,锁是当前类的Class对象。
  • 对于同步方法块,锁是Synchonized括号里配置的对象。

wait()作用

        wait(), notify(), notifyAll() 和 synchonized 需要搭配使用, 用于线程同步。

       wait()总是在一个循环中被调用,挂起当前线程来等待一个条件的成立。 Wait调用会一直等到其他线程调用notifyAll()时才返回。

       当一个线程在执行synchronized 的方法内部,调用了wait()后, 该线程会释放该对象的锁, 然后该线程会被添加到该对象的等待队列中(waiting queue), 只要该线程在等待队列中, 就会一直处于闲置状态, 不会被调度执行。 要注意wait()方法会强迫线程先进行释放锁操作,所以在调用wait()时, 该线程必须已经获得锁,否则会抛出异常。由于wait()在synchonized的方法内部被执行, 锁一定已经获得, 就不会抛出异常了。

notify()的功用

       wait(), notify(), notifyAll() 和 synchonized 需要搭配使用, 用于线程同步。

       当一个线程调用一个对象的notify()方法时, 调度器会从所有处于该对象等待队列(waiting queue)的线程中取出任意一个线程, 将其添加到入口队列( entry queue) 中. 然后在入口队列中的多个线程就会竞争对象的锁, 得到锁的线程就可以继续执行。 如果等待队列中(waiting queue)没有线程, notify()方法不会产生任何作用。

       notifyAll() 和notify()工作机制一样, 区别在于notifyAll()会将等待队列(waiting queue)中所有的线程都添加到入口队列中(entry queue)。

为什么wait() / notify() / notifyAll() 要放在synchronized的代码块中使用?

我们可以先来看看,不放在synchronized代码块中使用会有什么问题?

public static void main(String[] args) throws InterruptedException {
        Object obj = new Object();
        obj.wait();
        obj.notify();
    }

输出结果:

Exception in thread "main" java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at xxxx.main(XXX.java:254)

可以看到上面的代码输出结果,直接抛出异常了。下面就讲讲为何会出现这种问题。

wait() 和 notify()是用来实现多个线程之间的一个协调;wait() 表示让线程进入到阻塞状态,notify() 表示让阻塞的线程被唤醒, notifyAll() 则表述唤醒所有被阻塞的线程。 wait() 和 notify() 必然是成对出现的。 如果一个线程被wait()方法阻塞, 那么必然要另一个线程通过notify() 唤醒,从而实现多个线程之间的通信。

在多线程里面要实现多个线程之间的通信,除了管道以外,只能通过共享变量的方式来实现,也就是说第一个线程修改了公共共享变量,别的线程获得修改后的变量的值,从而完成数据的一个通讯;但是多线程的本身是具有并行执行的一个特性,也就是说,在同一个时间,多个线程是可以同时执行的,那么这种情况下,其它现在在访问共享变量之前,必须要知道,第一个线程已经修改了这个共享变量,否则就需要等待,或者是拿到原始数据,基于此基础上运输,产生数据不一致的场景;同时第一个线程在数据修改后,还需要把那个已经处于等待状态下的其它线程唤醒,所以在这种场景下,需要去实现线程之间的通信就必须要有一个静态条件,去控制多线程什么时候条件等待什么时候条件唤醒。而synchronized关键字就可以实现这样一个互斥条件,也就是在通过共享变量来实现多个线程通信的一个场景里面,参与通信的线程必须竞争到这共享变量的一个锁资源,才能够有资格对共享变量进行修改,那么修改完之后释放锁,其他线程就可以再次竞争同一个共享的锁,来获取修改之后的数据,从而完成线程之间的一个通信;所以这就是为什么wait() / notify() 必须需要在synchronized代码块中使用。

有了synchronized同步锁,就可以实现对于多个通信线程之间的一个互斥,从而实现条件等待和条件互斥的唤醒,另外为了避免wait() 和 notify()的错误使用,JDK强制的要求把wait() 和 notify() 写在了同步代码块中,否则就会抛出IllegalMonitorStateException。

	/* 	<pre>
     *     synchronized (obj) {
     *         while (&lt;condition does not hold&gt;)
     *             obj.wait(timeout, nanos);
     *         ... // Perform action appropriate to condition
     *     }
     * </pre>
     * This method should only be called by a thread that is the owner
     * of this object's monitor. See the {@code notify} method for a
     * description of the ways in which a thread can become the owner of
     * a monitor.
     * 
     * @param      timeout   the maximum time to wait in milliseconds.
     * @param      nanos      additional time, in nanoseconds range
     *                       0-999999.
     * @throws  IllegalArgumentException      if the value of timeout is
     *                      negative or the value of nanos is
     *                      not in the range 0-999999.
     * /

完结

<think>好的,用户想了解Java中Object类的waitnotify方法的底层实现机制,包括调用链和JVM源码分析。我需要结合提供的引用内容,整理出一个清晰的回答。 首先,根据引用1,waitnotify是Object类的方法,每个Java对象都有。接着引用3提到,在JVM源码中,notifynotifyAll不会立即释放ObjectMonitor,而是在执行monitorexit指令时才释放。引用4和5指出底层使用了park/unpark机制,在Linux下通过pthread_cond实现,线程在wait时会放弃CPU。引用5还提到了ObjectMonitor的exit方法。 需要分步骤解释:首先,Java层的方法调用如何进入JVM,然后JVM如何处理,涉及ObjectMonitor对象,以及线程状态的变化。还要说明锁的释放时机,可能存在的竞争问题,比如为什么要在同步块中使用这些方法。引用2中的代码示例展示了同步块的使用,但用户可能想知道底层如何保证同步。 需要组织信息,从Java API到JVM源码,再到操作系统层的实现。可能的结构是:Java层调用 -> JVM入口 -> ObjectMonitor机制 -> 线程挂起与唤醒 -> 系统调用。同时要结合引用中的各个点,比如park/unpark和pthread_cond的关系,以及monitorexit的重要性。 还需要注意用户可能对性能感兴趣,如引用4提到的线程放弃CPU,这对系统性能的影响。此外,引用3和5提到的entry set和ObjectWaiter节点,可以解释线程如何被管理。 最后生成相关问题时,要涵盖底层机制、使用注意事项、性能影响和与其他线程工具的比较。</think>### Java Object类wait/notify方法底层实现机制 #### 1. 核心调用链路 Java层调用 `wait()`/`notify()` 方法时,会通过以下路径进入JVM实现: ``` Java API -> JVM_ENTRY(jvm.cpp) -> ObjectMonitor::wait()/notify() ``` 具体过程: - **Java层调用**:`synchronized(obj) { obj.wait(); }` 触发`monitorenter`指令 - **JVM入口**:`jvm.cpp`中的`JVM_MonitorWait`和`JVM_MonitorNotify`方法 - **对象监视器**:通过`ObjectMonitor`对象管理线程状态(每个Java对象关联一个ObjectMonitor)[^3][^5] #### 2. ObjectMonitor关键结构 ```cpp class ObjectMonitor { volatile markOop _header; // 对象头 void* volatile _owner; // 持有锁的线程 ObjectWaiter* _WaitSet; // 等待队列(wait线程) ObjectWaiter* _EntryList; // 阻塞队列(等待锁的线程) // ... } ``` - `_WaitSet`存储处于WAITING状态的线程(通过wait()进入) - `_EntryList`存储等待获取锁的BLOCKED线程 #### 3. wait()实现步骤 ```cpp void ObjectMonitor::wait(jlong millis, bool interruptible) { // 1. 构造ObjectWaiter节点 ObjectWaiter node(Self); // 2. 将线程加入_WaitSet队列 AddWaiter(&node); // 3. 释放锁并触发monitorexit exit(true, Self); // 4. 调用park挂起线程(底层通过pthread_cond_wait实现) park(millis); // 5. 被唤醒后重新竞争锁 enter(Self); } ``` 关键点: - 调用`wait()`会立即释放对象锁(通过`exit()`触发monitorexit) - 线程挂起通过操作系统级条件变量实现(Linux下`pthread_cond_wait`)[^4] #### 4. notify()实现步骤 ```cpp void ObjectMonitor::notify(TRAPS) { // 1. 从_WaitSet移出第一个等待线程 ObjectWaiter* iterator = DequeueWaiter(); // 2. 将线程转移到_EntryList或直接唤醒 if (Policy == 0) { // 默认策略:移入EntryList enqueue(iterator); } else { // 策略1:直接唤醒 unpark(iterator->_thread); } } ``` 重要特性: - `notify()`不会立即释放锁,实际锁释放发生在同步块结束的`monitorexit`指令 - 被唤醒的线程需要重新竞争锁才能继续执行[^5] #### 5. 操作系统层实现 - **挂起**:通过`pthread_cond_wait()`让出CPU,线程进入WAITING状态 - **唤醒**:`pthread_cond_signal()`触发线程调度 - **性能优势**:挂起的线程不占用CPU时间片[^4] #### 6. 典型问题案例 引用[2]中的代码存在严重问题: ```java synchronized (flag) { flag = "false"; // 修改锁对象引用导致后续notify失效 flag.notify(); } ``` 当修改`flag`引用后,其他线程的`synchronized(flag)`可能锁定不同对象,导致`notify()`失效。这是典型的对象锁引用逃逸问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值