Java并发之Thread.join()

本文通过实例解析Java中Thread类的join方法,展示如何使用join方法使主线程等待子线程完成,以及join方法的内部实现机制。
前言:刚刚开始学习多线程的Thread的时候就知道了join这个方法,一直对它只是懵懂的认知。Waits for this thread to die是这个方法的作用。
  1. 看一个例子
public class SynchronizedClassClass 
        implements Runnable{

    static SynchronizedClassClass synchronizedObjectCodeBlock1 
            = new SynchronizedClassClass();
    static int i = 0;

    public static void main(String[] args) {
        Thread t1 = new Thread(synchronizedObjectCodeBlock1);
        t1.start();
        try {
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i);
    }

    @Override
    public void run() {
        method();
    }

    public void method(){
        synchronized (SynchronizedClassClass.class){
            for (int j = 0; j < 100000; j++) {
                i++;
            }
        }
    }
}

当调用了t1.join()方法之后结果返回的值一定是100000,如果没有调用返回的值就是随机的,这个就可以得出线程调用join方法之后,该方法后的方法会等待线程执行完之后再执行后续的代码,对于当前的类就是等t1线程执行完run方法中的所有的代码后才会执行输出的动作
2. join的源码

public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
    public final synchronized void join(long millis, int nanos)
    throws InterruptedException {
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }
        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }
        join(millis);
    }
	public final void join() throws InterruptedException {
        join(0);
    }

通过查看源码可以知道join()join(long millis, int nanos)最终都是调用join(long millis),对于join(long millis)来说中间的逻辑就是如果给定的等待时间是0的话,表示主线程永远的等待,直到子线程执行完毕。至于t1.join()为什么不是子线程的暂停等待,而是主线程,借用大神的理解是调用的join()然后调用了wait方法其中wait是Object类的方法,把对象t1当成是一个普通的对象,调用wait方法是让调用方法的线程挂起,和调用的是哪个对象上的wait方法没关系。

<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的对象上的通知(通过notify或notifyAll)。这种等待状态本身不一定代表问题,因为很多并发设计会使用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、付费专栏及课程。

余额充值