Java 线程wait()之后一定要notify()才能唤醒吗?

本文探讨了Java中线程的wait()方法使用细节及如何正确地与notify()方法配合,通过具体示例代码展示了不同情况下线程的状态变化,解释了在特定条件下线程为何能自动退出等待状态。

研究这个问题之前先要搞清楚线程有哪些状态?

1. 新建状态(New)         : 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
2. 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
3. 运行状态(Running) : 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
4. 阻塞状态(Blocked)  : 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
    (01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
    (02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
    (03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5. 死亡状态(Dead)    : 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

 

清楚了线程的状态之后,我们要搞懂wait()方法是谁来调用,执行wait()方法后,哪个线程进入等待的队列?

可参考java中wait()与锁的关系

接下来进入正题,Java 线程wait()之后一定要notify()才能唤醒吗?

我们来看一段代码:

class Person{
    private String name;

}
public class ThreadA
{
    //public static Person person=new Person();
    public static void main(String[] args)
    {
        ThreadB b = new ThreadB();
        ThreadC c = new ThreadC();
        c.setName("c线程");
        b.setName("b线程");
        c.start();
        System.out.println(Thread.currentThread().getName()+"is start....");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        synchronized(c)
        {
            try
            {
                System.out.println(Thread.currentThread().getName()+" synchronized method is beginning");
                System.out.println(c.isAlive());
                Thread.sleep(1000);
                System.out.println(c.isAlive());
                System.out.println(Thread.currentThread().getName()+ " is waiting");
                c.wait();
                System.out.println("now back to "+
                        Thread.currentThread().getName());
                b.start();
            }
            catch(InterruptedException e)
            {
            }
        }
    }
}
class ThreadB extends Thread
{
    int total;
    public void run()
    {
        System.out.println(Thread.currentThread().getName()+" is running..");
        synchronized(this)
        {

            for(int i=0;i<10;i++)
            {
                total +=i;
            }
            System.out.println("total is "+total);
        }
    }
}
class ThreadC extends Thread
{
    //private Person person;
    int sum=1;
    public void run()
    {
        System.out.println(Thread.currentThread().getName()+"is running..");
        synchronized(this)
        {
            System.out.println(Thread.currentThread().getName()+" synchronized method is beginning");
            for(int i=1;i<10;i++)
            {
                sum *=i;
            }
            System.out.println(Thread.currentThread().getName()+" synchronized method is ending");
//            notify();
        }
        for (int i=0;i<50;i++){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i);
        }
        System.out.println("c线程运行结束");
    }
}

运行结果:

mainis start....
c线程is running..
c线程 synchronized method is beginning
c线程 synchronized method is ending
0
1
2
3
4
5
6
7
8
main synchronized method is beginning
true
9
10
11
12
13
14
15
16
17
true
main is waiting
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
c线程运行结束
now back to main
b线程 is running..
total is 45

上面的代码运行结果结合文章开头的线程状态是这样的:C线程和main线程先都由New->Runnable->Running,然后C线程先拿到了c同步对象锁,所以可以优先于main线程进入synchronized同步块,仍然保持Running状态,而main线程由Running->同步阻塞(锁定Blocked)状态。C线程执行完synchronized同步块后,立即释放c同步对象锁。此时main线程获得锁,锁定Blocked->Runnable->Running,进入synchronized同步块。这时候,C线程还在执行run()方法,main线程执行到c.wait()时,又从Running->等待Blocked,同时释放c同步对象锁。关键时候来了,C线程执行完run()方法后,C线程正常结束,以线程对象c运行的wait()线程(这里也就是main线程)退出等待队列,相当于唤醒,notify()/notifyAll(),所以main线程从等待Blocked->锁定Blocked,再获取同步对象锁,锁定Blocked->Runnable->Running。经过验证,这里的唤醒相当于notifyAll(),这里代码我就不贴了,有兴趣可以去验证一下。

所以,倘若是main线程先拿到c同步对象锁,运行结果也是会正常退出。但是如果我把C线程循环Thread.sleep(100)注释掉,结果可能就不一样了:

class ThreadC extends Thread
{
    //private Person person;
    int sum=1;
    public void run()
    {
        System.out.println(Thread.currentThread().getName()+"is running..");
        synchronized(this)
        {
            System.out.println(Thread.currentThread().getName()+" synchronized method is beginning");
            for(int i=1;i<10;i++)
            {
                sum *=i;
            }
            System.out.println(Thread.currentThread().getName()+" synchronized method is ending");
            notify();
        }
/*        for (int i=0;i<50;i++){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i);
        }*/
        System.out.println("c线程运行结束");
    }
}

运行结果:

mainis start....
c线程is running..
c线程 synchronized method is beginning
c线程 synchronized method is ending
c线程运行结束
main synchronized method is beginning
false
false
main is waiting

因为main线程的c.wait()方法是在C线程正常结束后执行的,C线程正常结束时并没有可以唤醒的以c线程对象wait()的线程,等待队列是空的。所以之后当main线程执行的c.wait()方法进入到等待队列中,已经没有谁去唤醒它了,所以main线程将会一直处于等待状态,程序一直运行,无法正常退出。

 

参考博客:

https://blog.youkuaiyun.com/nmyangym/article/details/7850882#commentsedit

https://blog.youkuaiyun.com/Deronn/article/details/80450959

https://www.cnblogs.com/happy-coder/p/6587092.html

https://blog.youkuaiyun.com/u011784767/article/details/51426449

https://www.cnblogs.com/kevin-yuan/p/4112434.html

<think>好的,我现在需要回答用户关于Java线程waitnotify的锁释放和获取机制的问题。首先,我得回忆一下相关的知识点,确保自己理解正确。 用户的问题是,当线程调用wait方法notify唤醒后,是否需要重新获取锁。根据之前学过的知识,当线程进入wait状态时,会释放持有的锁,这样其他线程可以获取该锁。当被notify唤醒后,线程需要重新获取锁才能继续执行。这一点我记得在参考资料中有提到,比如引用[1]指出wait会释放锁,而notify不会释放。所以被唤醒线程必须重新竞争锁。 接下来,我需要理清整个过程。线程在同步代码块中调用wait,这时会释放锁并进入等待队列。其他线程调用notifynotifyAll后,等待的线程会被移到阻塞队列,等待锁的释放。当持有锁的线程退出同步块释放锁时,这些等待的线程才有机会获取锁,其中一个会成功获取并继续执行。 这里需要注意,notify只是通知,并不释放锁。唤醒线程需要等待当前线程释放锁后才能获取。比如引用[2]的例子,通过sleep确保其他线程进入wait状态并释放锁,这样notifyAll才能被正确执行。而引用[3]提到notifynotifyAll的区别在于唤醒线程数量,但所有被唤醒线程都需要重新获取锁。 还有,当线程唤醒后,可能需要检查条件是否满足,因为可能存在虚假唤醒的情况。因此,通常使用循环来检查条件,而不是简单的if语句。这一点虽然用户没问,但可能相关。 最后,总结回答的结构,分步骤说明wait释放锁,notify不释放,唤醒后的锁获取过程,并引用相关资料中的例子和解释来支持观点。同时,生成相关问题,帮助用户进一步理解。</think>### Java线程wait/notify的锁机制解析 #### 1. **wait()调用时的锁释放** 当线程调用`wait()`方法时,会**立即释放当前持有的锁**,并进入等待状态。这一过程要求线程必须**在同步代码块(synchronized块)中调用`wait()`**,否则会抛出`IllegalMonitorStateException`。释放锁的目的是让其他线程有机会获取锁并修改共享资源的状态[^1][^4]。 例如: ```java synchronized (lock) { while (conditionNotMet) { lock.wait(); // 释放锁,进入等待队列 } // 被唤醒后继续执行 } ``` #### 2. **notify()/notifyAll()的作用** - `notify()`:随机唤醒一个在锁上等待的线程,将其移到**阻塞队列**,但**不释放锁**。被唤醒线程需要等待当前线程退出同步块后,才能尝试获取锁[^3]。 - `notifyAll()`:唤醒所有在锁上等待的线程,同样**不释放锁**。这些线程需要竞争获取锁,最终只有一个线程成功[^3]。 示例场景: ```java synchronized (lock) { // 修改共享资源 lock.notify(); // 唤醒一个线程(不释放锁) // 同步块结束后锁被释放 } ``` #### 3. **被唤醒线程的锁获取** 当线程被`notify()`唤醒后,**必须重新获取锁**才能继续执行。具体流程如下: 1. 线程从等待队列转移到阻塞队列。 2. 当持有锁的线程**退出同步块**时,锁被释放。 3. 被唤醒线程与其他可能存在的线程竞争锁,最终只有一个成功获取。 4. 成功获取锁的线程从`wait()`返回,继续执行后续代码[^3]。 #### 4. **关键对比:wait() vs sleep()** | 行为 | wait() | sleep() | |---------------|--------------------------------|---------------------------| | **锁释放** | 立即释放锁 | 不释放锁 | | **调用位置** | 必须在同步块中 | 任意位置 | | **唤醒条件** | 依赖notify()/notifyAll()或超时 | 仅依赖超时 | | **用途** | 线程间协作 | 单纯暂停执行 | #### 5. **实际应用注意事项** - **虚假唤醒**:即使没有`notify()`,线程也可能被唤醒。因此,`wait()`通常需要放在`while`循环中检查条件。 - **死锁风险**:若唤醒后未正确设计锁竞争逻辑,可能引发死锁。例如,唤醒线程未及时释放锁,导致其他线程无法执行。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值