JAVA多线程-Object.wait(),Object.notify(),Object.notifyAll()

本文深入探讨Java中线程的同步机制,包括wait(), notify()和notifyAll()方法的使用,以及它们与synchronized关键字的关系。通过代码实操演示如何实现线程间的交替打印,解析线程状态变化及唤醒策略。

初步理解

wait()

  1. 使线程停止运行,进入等待队列,会立刻释放对象锁
  2. wait(0) 代表的是无限期的等待,不能自动唤醒。
  3. java.lang.Object类方法。

notify()

  1. 唤醒一个正在等待当前对象的waiting线程,遵循FIFO(先进先出)策略。
  2. notify()调用后,并不是马上就释放对象锁,而是在相应的synchronized{…}语句块执行结束后,自动释放锁后,JVM在wait()对象锁的线程中随机选取一线程赋予其对象锁,唤醒线程,继续执行。这样就提供了在线间同步、唤醒的操作
  3. java.lang.Object类方法。

notifyAll()

  1. 唤醒正在等待当前对象的所有等待线程。
  2. 默认的唤醒策略是:LIFO(后进先出)。
  3. java.lang.Object类方法。

wait()与notify()

  1. Obj.wait(),与Obj.notify()必须与synchronized(Obj)一起使用,也就是wait()是针对已经获取了Obj锁进行操作。
  2. 语法角度来说就是Obj.wait(),Obj.notify()必须在、synchronized(Obj){…}语句块内。
  3. 功能上来说wait就是指在该线程获取对象锁后,主动释放对象锁,同时本线程休眠。notify()就是对对象锁的唤醒操作。

wait()与sleep()
共同点

  1. 都可以指定线程阻塞的时间。
  2. 都可以通过interrupt()方法打断(不建议使用)线程的暂停状态 ,从而使线程立刻抛出InterruptedException。如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。
    需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException 。

不同点

  1. sleep()是Thread类的方法,wait()是Object类的方法。
  2. sleep()方法没有释放锁,而wait()方法释放了锁。
  3. wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。

执行notify()后,waiting线程可能的状态

线程a先抢到了对象o的锁,然后wait,然后b抢到了o的锁,然后b中调用o.notify并释放锁,此时a是running状态还是blocked状态??
如果b在执行完notify()后没有释放锁则线程a是阻塞等待,
如果线程b执行完同步代码块(释放锁)后,则线程a就是就绪态,不一定是运行态

代码实操,三线程打印ABC

建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。这个问题用Object的wait(),notify()就可以很方便的解决。

public class T08WaitNotify {

    class T implements Runnable{
        // 自定义线程名称
        private String name;
        // 前一个obj
        private Object prev;
        // 当前obj
        private Object self;

        T(String name, Object prev, Object self){
            this.name = name;
            this.prev = prev;
            this.self = self;
        }

        @Override
        public void run() {
            int count = 0;
            while (count < 10) {
                synchronized (prev) {
                    synchronized (self) {
                        System.out.print(name);
                        count++;
                        self.notify();
                    }
                    try {
                        prev.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        }
    }

    public static void main(String[] args) throws InterruptedException {

        T08WaitNotify obj = new T08WaitNotify();

        Object oa = new Object();
        Object ob = new Object();
        Object oc = new Object();

        T a = obj.new T("A", oc, oa);
        T b = obj.new T("B", oa, ob);
        T c = obj.new T("C", ob, oc);

        Thread ta = new Thread(a);
        Thread tb = new Thread(b);
        Thread tc = new Thread(c);

        ta.start();
        Thread.sleep(1000); // 确保按照A、B、C顺序执行
        tb.start();
        Thread.sleep(1000);
        tc.start();
        Thread.sleep(1000);
    }
}

代码流程说明
A线程启动,依次取得c、a锁,打印A,执行notify()唤醒任意一个a对象等待池中的线程,待synchronized(self){}块执行完后释放a锁,执行wait(),将A线程放入c对象相关的等待池中等待被唤醒同时释放c锁; 1秒后,B线程启动,依次取得a、b锁,打印B,执行notify()唤醒任意一个b对象等待池中的线程,待synchronized(self){}块执行完后释放b锁,执行wait(),将B线程放入a对象相关的等待池中等待被唤醒,同时释放a锁;1秒后,C线程启动,依次取得b、c锁,打印C,执行notify()唤醒任意一个c对象等待池中的线程(此时A线程被唤醒),待synchronized(self){}块执行完后释放c锁,执行wait(),将C线程放入b对象相关的等待池中等待被唤醒,同时释放b锁。

至此,完成一次ABC的打印。继续执行,从A线程开始,执行各自线程中的下一次循环。

特别说明
Java源码中wait()默认调用的是wait(0),表明线程无限等待,只能被其他线程唤醒。

参考
https://blog.youkuaiyun.com/u010002184/article/details/82912225
https://blog.youkuaiyun.com/hj1997a/article/details/84284973

<think> 我们正在分析一个Java线程转储(thread dump)中出现的`at java.lang.Object.wait(Native Method)`调用栈信息。 根据引用[1]和[2],线程处于WAITING状态,在等待某个对象的监视器(锁)。引用[1]中的例子是Tomcat的工作线程在等待任务,属于正常情况。但引用[2]和[3]则提到了死锁情况:当多个线程互相持有对方所需的锁时,就会发生死锁。 用户提供的调用栈信息片段: "at java.lang.Object.wait(Native Method)" "waiting on <0x0c377b11> (a java.lang.Object)" 这表示该线程在等待地址为0x0c377b11的对象上的通知(通过notifynotifyAll)。这种等待状态本身不一定代表问题,因为很多并发设计会使用wait/notify机制。但是,如果线程长期处于这种状态且没有收到通知,或者与其他线程形成了循环等待(死锁),那就是问题。 我们需要分析: 1. 为什么线程会进入WAITING状态?是设计如此还是异常情况? 2. 是否存在死锁? 根据引用[3],我们可以通过线程转储来检测死锁。在转储中,如果存在死锁,通常会有明确的提示(如Found 1 deadlock)以及各线程的锁持有和等待关系。 解决方案: - 如果是正常等待,则无需处理。 - 如果是死锁,则需要修改代码以避免循环等待。 另外,引用[4]展示了一个名为"Attach Listener"的守护线程,它在等待条件,这是JVM内部线程,通常无需担心。 现在我们针对用户的问题进行回答: 问题:出现`at java.lang.Object.wait(Native method)`等调用栈信息的问题原因及解决方案。 原因可能包括: 1. 正常的线程同步等待(例如,线程在等待任务队列中的任务)。 2. 死锁:多个线程互相等待对方释放锁。 3. 活锁或饥饿:虽然线程在等待,但可能由于设计问题永远得不到唤醒(如忘记调用notify)。 解决方案步骤: 1. 获取完整的线程转储(使用jstack或jvisualvm等工具)。 2. 查找线程转储中的死锁信息(通常会有"Found one Java-level deadlock"这样的提示)。 3. 如果没有死锁,检查该线程的上下文,确定它等待的条件是什么,以及是否有其他线程会唤醒它。 4. 检查代码中wait/notify的使用是否符合规范(例如,是否在同步块内调用wait,是否有可能导致永久等待的条件)。 如果确定是死锁,解决死锁的常用方法: - 避免嵌套锁:尽量只持有一个锁。 - 按固定顺序获取锁。 - 使用带有超时的等待(如Object.wait(long timeout))。 注意:用户提供的引用[3]中有一个死锁的代码示例,其中两个线程以相反的顺序请求两个锁,导致死锁。可以以此为例分析。 因此,回答将按照以下结构: 1. 解释调用栈信息的含义。 2. 分析可能的原因(正常等待、死锁、永久等待)。 3. 提供解决方案(包括如何检测死锁,如何修改代码)。 4. 给出预防建议。 同时,按照要求生成相关问题。 </think> ### Java线程调用栈`java.lang.Object.wait(Native Method)`问题分析与解决 #### 问题原因分析 1. **线程等待状态** `at java.lang.Object.wait(Native Method)`表明线程正处于`WAITING`状态(引用[1]),这是Java对象监视器(Monitor)机制的核心表现: - 当一个线程调用`Object.wait()`时,会释放持有的对象锁(`<0x0c377b11>`),并进入阻塞状态 - 该线程会一直等待直到其他线程调用`notify()`/`notifyAll()` - 常见场景:线程池任务等待、生产者-消费者模型 2. **潜在死锁风险**(引用[2][3]) 当出现以下调用栈组合时需警惕死锁: ```java // 线程A locked <0x0000000780f6aa80> waiting to lock <0x0000000780f6aab8> // 线程B locked <0x0000000780f6aab8> waiting to lock <0x0000000780f6aa80> ``` 形成循环等待链:线程A持有锁A等待锁B,线程B持有锁B等待锁A 3. **其他常见原因** - 资源竞争:多个线程争用同一对象锁 - 未唤醒:`wait()`后缺少对应的`notify()`调用 - 同步设计缺陷:锁粒度不合理或嵌套过多 --- #### 解决方案 1. **死锁检测与定位** 使用`jstack`或VisualVM分析线程转储: ```shell jstack -l <PID> > thread_dump.txt ``` 查找输出中的`Found 1 deadlock`提示和锁依赖链(引用[2]) 2. **修复死锁代码**(引用[3]示例) - 统一锁获取顺序:两个线程按相同顺序请求锁 ```java // 修改前(死锁) new Thread(new HoldLockThread("LockA", "LockB")).start(); new Thread(new HoldLockThread("LockB", "LockA")).start(); // 修改后(统一顺序) new Thread(new HoldLockThread("LockA", "LockB")).start(); new Thread(new HoldLockThread("LockA", "LockB")).start(); ``` - 使用超时等待:避免永久阻塞 ```java synchronized(lock){ lock.wait(5000); // 5秒超时 } ``` 3. **优化同步机制** - 用`java.util.concurrent`代替`synchronized/wait` ```java Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); condition.await(5, TimeUnit.SECONDS); // 带超时的等待 ``` - 减小同步代码块范围 - 避免嵌套锁(锁内再请求新锁) 4. **线程池调优**(引用[1]场景) - 检查Tomcat线程配置:`maxThreads`、`acceptCount` - 监控任务队列积压情况 - 避免任务阻塞I/O线程 --- #### 排查流程图 ```mermaid graph TD A[发现WAITING状态线程] --> B{检查wait()调用点} B -->|正常等待| C[确认notify/notifyAll逻辑] B -->|异常阻塞| D[获取线程转储] D --> E[查找死锁提示] E -->|Found deadlock| F[分析锁依赖环] E -->|无死锁| G[检查资源竞争] F --> H[统一锁获取顺序] G --> I[减小锁粒度/改用并发工具] ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值