Java Thread中的join()方法解释

Java Thread类中的join()方法用于阻塞当前线程,等待指定线程执行完毕后再继续执行。join内部通过调用线程对象的wait()方法实现,释放持有锁,允许其他等待该锁的线程执行。在使用join()时需要注意InterruptedException的捕获。

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

Join作用

join是定义在Thread类中的方法,作用是阻塞当前线程的执行,等到被调用join的线程对象执行完毕才执行继续执行当前线程。在Java源码中的定义如下:

public final void join() throws InterruptedException {
    join(0);
}

public final synchronized void join(long millis) throws InterruptedException

接下来我们先来看看join的简单使用,这样能更好的帮助理解:

public static void main(String[] args) {
    Thread thread = new Thread(() -> {
        for (int i = 0; i < 5; i++) {
            System.out.println("执行" + Thread.currentThread().getName() + i);
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }, "thread");
    thread.start();
    try {
        thread.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("线程" + Thread.currentThread() + "执行结束");

    }

上面代码的输出结果将会是:

执行thread0
执行thread1
执行thread2
执行thread3
执行thread4
线程Thread[main,5,main]执行结束

所以我们可以看出,主线程执行到join的时候,会被阻塞直到thread这个子线程执行完才继续执行主线程,也就是说,thread.join()方法阻塞调用此方法的线程(callingthread)进入TIMED_WAITING状态,直到线程t完成,此线程再继续。

当我们再回到刚才join方法的源码时,发现最终是调用到

public final synchronized void join(long millis) throws InterruptedException

这个方法,它是无参那个方法的重载。这方法是个同步方法,锁对象是调用join的线程对象本身,为什么要加这个synchronized呢,继续往里看可以看到:

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;
            }
        }

其实它内部是调用了该线程对象的wait方法,而我们知道,wait,notify这些方法必须在同步代码中调用,否则会抛出IllegalMonitorStateException异常,所以这里直接用了本身的线程对象,这也说明了一个问题,join()的执行肯定获得本线程对象锁。

到这里,join的用法其实也就这么简单,但是延伸一个问题,就是调用join时,会释放锁吗?如果会,释放的是哪个锁?是调用join时所处的线程所持有的锁还是thread1这个线程对象锁?

答案是:会释放锁,释放的是调用join的这个线程对象的锁。也就是说,本例子中调用thread.join()时,如果该句代码执行在以thread对象为锁的同步代码中,那么当cpu把执行权给了其他thread这个线程中时,该线程中如果也有以thread对象为锁的同步代码块,那么该代码块会获得锁并得到执行。我们从

public final synchronized void join(long millis) throws InterruptedException

的内部实现也可以看出来,因为调用的是该线程对象的wait方法,并且这个wait是包在以thread这个对象为锁的同步方法中的,我们知道执行wait方法是会释放锁的,不明白可看我另一篇wait与notify的文章,所以我们可以得出上述结论。接下来我们以一个例子来做验证:

public class JoinTest {

    Object lock = new Object();

    public void main() throws InterruptedException {
        Thread1 thread1 = new Thread1();
        Thread2 thread2 = new Thread2(thread1);

        thread1.start();
        thread2.start();

        Thread.sleep(3000L);
        synchronized (thread1) {
            System.out.println("执行" + Thread.currentThread().getName() + "中的同步代码块");
        }
    }

    class Thread1 extends Thread {
        @Override
        public void run() {
            System.out.println("开始执行" + Thread.currentThread().getName() + "...");
            try {
                while (true) {
                    System.out.println("执行" + Thread.currentThread().getName() + "中的同步代码块");
                    Thread.sleep(1000L);
                }
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "被interrupt,此时该线程的interpret标记位为:" + Thread.interrupted());
            }
            System.out.println("结束执行" + Thread.currentThread().getName() + "!!!");
        }
    }

    class Thread2 extends Thread {
        private Thread1 thread1;

        public Thread2(Thread1 thread1) {
            this.thread1 = thread1;
        }

        @Override
        public void run() {
            System.out.println("开始执行" + Thread.currentThread().getName() + "...");
            try {
                synchronized (thread1) {
                    thread1.join();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("结束执行" + Thread.currentThread().getName() + "!!!");
        }
    }
}

本示例中,定义了2个子线程,Thread1中做了一个无限循环操作,Thread2中调用了Thread1线程对象的join方法,这样的话,thread2就得等到thread1执行完才能继续执行。这时thread2线程中,调用thread1.join()方法是在同步代码块中。紧接着我们在3秒后在主线程中也用这个对象作为锁执行一段代码来看一下结果:

开始执行Thread-0...
执行Thread-0中的同步代码块
开始执行Thread-1...
执行Thread-0中的同步代码块
执行Thread-0中的同步代码块
执行main中的同步代码块
执行Thread-0中的同步代码块
执行Thread-0中的同步代码块
执行Thread-0中的同步代码块
执行Thread-0中的同步代码块
执行Thread-0中的同步代码块

    ...

这时候我们可以很清楚的看到,在thread2执行到thread1.join()方法时,如果thread1这个对象锁如果没有被释放的话,主线程中执行main中的同步代码块这段代码是执行不到的,但是我们看到结果确实执行到了,这时候我们可以大胆的说,当thread2中调用thread1.join()方法时,会释放thread1这个对象锁。我们也可以做一个小改动来验证一下,如果我们这时候把主线程和thread2线程中的同步代码块的锁都换成别的对象的话,结果将是thread1线程一直执行,而主线程中的执行main中的同步代码块这段代码是永远得不到执行的。我们来验证一下:

package com.lianwenhong;

public class JoinTest {

    Object lock = new Object();

    public void main() throws InterruptedException {
        Thread1 thread1 = new Thread1();
        Thread2 thread2 = new Thread2(thread1);

        thread1.start();
        thread2.start();

        Thread.sleep(3000L);
        synchronized (lock) {
            System.out.println("执行" + Thread.currentThread().getName() + "中的同步代码块");
        }
    }

    class Thread1 extends Thread {
        @Override
        public void run() {
            System.out.println("开始执行" + Thread.currentThread().getName() + "...");
            try {
                while (true) {
                    System.out.println("执行" + Thread.currentThread().getName() + "中的同步代码块");
                    Thread.sleep(1000L);
                }
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "被interrupt,此时该线程的interpret标记位为:" + Thread.interrupted());
            }
            System.out.println("结束执行" + Thread.currentThread().getName() + "!!!");
        }
    }

    class Thread2 extends Thread {
        private Thread1 thread1;

        public Thread2(Thread1 thread1) {
            this.thread1 = thread1;
        }

        @Override
        public void run() {
            System.out.println("开始执行" + Thread.currentThread().getName() + "...");
            try {
                synchronized (lock) {
                    thread1.join();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("结束执行" + Thread.currentThread().getName() + "!!!");
        }
    }
}

输出的结果:

开始执行Thread-0...
开始执行Thread-1...
执行Thread-0中的同步代码块
执行Thread-0中的同步代码块
执行Thread-0中的同步代码块
执行Thread-0中的同步代码块
执行Thread-0中的同步代码块
执行Thread-0中的同步代码块
执行Thread-0中的同步代码块
执行Thread-0中的同步代码块
执行Thread-0中的同步代码块
执行Thread-0中的同步代码块
执行Thread-0中的同步代码块
    ...

结局正是我们所预料的那样。

还有一个值得我们注意的是,在我们查看源码的时候会发现,join()方法还会抛出一个InterruptedException,所以我们在使用thread.join()方法的时候需要注意捕获这个异常。这个异常的抛出是因为有可能别的线程会调用thread2.interrupt()方法,具体原因及作用我们在interrupt这篇笔记中说。

至此我们总结:
1.join是定义在Thread类中的方法,作用是阻塞当前线程的执行,等到被调用join的线程对象执行完毕才执行继续执行当前线程。
2.当thread.join()被调用时,如果调用的线程中持有了thread对象锁会被释放。

希望本文章能对Thread中的join()还不熟悉的朋友有点参考意义。

<think>我们正在讨论JavaThread类的join方法。根据引用资料,join方法的主要作用是让一个线程等待另一个线程完成。下面我将详细解释join方法的作用、工作原理、使用方式以及应用场景。 ### 一、join方法的作用 1. **线程等待**:当在某个线程(如主线程)中调用`thread.join()`时,当前线程会进入等待状态,直到目标线程(即thread)执行完毕[^1][^2]。 2. **协调执行顺序**:通过join方法,可以控制多个线程的执行顺序,确保某些线程在其他线程之前完成[^4]。 ### 二、join方法的工作原理 join方法底层是基于Java的等待/通知机制(wait/notify)实现的: 1. 当调用`thread.join()`时,当前线程(假设是主线程)会获取thread对象的锁(因为join方法内部使用`synchronized`修饰)[^2]。 2. 然后检查thread是否存活(通过`isAlive()`方法),如果存活,则调用`wait()`方法使当前线程进入等待状态[^2]。 3. 当目标线程thread执行完毕(进入TERMINATED状态)时,JVM会自动调用thread的`notifyAll()`方法,唤醒所有等待在该thread对象上的线程[^2]。 ```java // join方法源码片段(Java 17) public final void join() throws InterruptedException { join(0); } public final synchronized void join(long millis) throws InterruptedException { // 检查当前线程是否已被中断 if (Thread.interrupted()) { throw new InterruptedException(); } // 如果超时时间为0,则持续等待直到目标线程结束 if (millis == 0) { while (isAlive()) { wait(0); } } else { // 带超时的等待 while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } } ``` ### 三、join方法的使用方式 #### 1. 基本用法:等待单个线程结束 ```java Thread worker = new Thread(() -> { // 模拟耗时操作 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("子线程执行完毕"); }); worker.start(); // 主线程等待worker结束 worker.join(); // 主线程在此阻塞,直到worker运行结束[^1] System.out.println("主线程继续执行"); ``` #### 2. 等待多个线程结束 ```java List<Thread> threads = new ArrayList<>(); for (int i = 0; i < 5; i++) { Thread t = new Thread(/* 任务 */); t.start(); threads.add(t); } // 主线程等待所有子线程结束 for (Thread t : threads) { t.join(); // 依次等待每个线程结束[^3] } System.out.println("所有子线程已结束"); ``` #### 3. 带超时的等待 ```java Thread worker = new Thread(/* 长时间任务 */); worker.start(); worker.join(3000); // 最多等待3秒[^5] if (worker.isAlive()) { System.out.println("worker仍在运行,主线程不再等待"); // 可选项:中断worker线程 worker.interrupt(); } ``` ### 四、应用场景 1. **结果依赖** 主线程需要获取子线程的处理结果时(如计算值、状态等),必须等待子线程结束[^4]。 ```java class ResultHolder { int result; } ResultHolder holder = new ResultHolder(); Thread calculator = new Thread(() -> { holder.result = compute(); // 耗时计算 }); calculator.start(); calculator.join(); // 等待计算完成 System.out.println("计算结果:" + holder.result); ``` 2. **资源有序释放** 需要确保子线程释放资源后,主线程才能继续操作(如文件句柄、网络连接)[^4]。 ```java Thread fileProcessor = new Thread(() -> { try (FileInputStream fis = new FileInputStream("data.txt")) { // 处理文件 } }); fileProcessor.start(); fileProcessor.join(); // 确保文件已关闭 // 安全删除文件 Files.delete(Paths.get("data.txt")); ``` 3. **任务分阶段执行** 多阶段任务中,下一阶段必须等待前一阶段的所有子任务完成[^3]。 ```java // 第一阶段:数据加载 List<Thread> loadTasks = startDataLoadTasks(); for (Thread t : loadTasks) t.join(); // 第二阶段:数据处理 List<Thread> processTasks = startDataProcessTasks(); for (Thread t : processTasks) t.join(); // 第三阶段:结果汇总 aggregateResults(); ``` ### 五、注意事项 1. **中断处理** 若等待过程中当前线程被中断(如调用`interrupt()`),`join()`会立即抛出`InterruptedException`并清除中断状态[^5]。 ```java try { worker.join(); } catch (InterruptedException e) { // 中断处理:清理资源并退出 Thread.currentThread().interrupt(); // 恢复中断状态 } ``` 2. **锁释放** 调用`join()`不会释放当前线程持有的其他对象锁(因为join方法只获取目标线程对象的锁)[^2]。 3. **性能影响** 过度使用join可能导致线程串行化,失去并发优势。此时可考虑`java.util.concurrent`包中的高级工具(如`CountDownLatch`、`CyclicBarrier`)[^1]。 ```mermaid graph TD A[主线程调用t.join] --> B{线程t是否存活?} B -->|是| C[主线程进入WAITING状态] B -->|否| D[主线程继续执行] C --> E[线程t终止] E --> F[JVM调用t.notifyAll] F --> D ``` > **最佳实践**:优先使用`java.util.concurrent`工具替代原生join,尤其在需要等待多个线程的场景。 §§相关问题§§ 1. 为什么`join()`方法会导致调用线程阻塞? 2. `join()`与`sleep()`在阻塞线程时有何本质区别? 3. 如何用`CountDownLatch`替代`join()`实现更灵活的线程等待? 4. 调用`join()`后主线程被中断会发生什么?</think>### Java Thread.join() 方法详解及使用场景 #### 一、核心作用 `join()` 是 Java `Thread` 类的关键方法,用于**线程同步控制**: 1. **阻塞等待**:调用线程(如主线程)会阻塞,直到目标线程执行完毕[^1][^2] 2. **执行顺序管理**:确保多个线程按特定顺序执行[^3] 3. **结果依赖**:主线程获取子线程处理结果前等待其完成[^4] #### 二、工作原理 ```mermaid graph TD A[主线程调用 thread.join] --> B{目标线程是否存活?} B -->|是| C[主线程进入WAITING状态] B -->|否| D[主线程继续执行] C --> E[目标线程终止] E --> F[JVM调用线程的notifyAll] F --> G[主线程被唤醒] ``` 底层实现基于**等待/通知机制**: ```java // 简化版源码实现 public final synchronized void join(long millis) { while (isAlive()) { // 检查目标线程是否存活 wait(millis); // 调用线程进入等待状态 } } ``` #### 三、使用方式 ##### 1. 基础用法:等待单个线程 ```java Thread worker = new Thread(() -> { System.out.println("子线程开始工作"); try { Thread.sleep(2000); } catch (InterruptedException e) { /* 处理中断 */ } System.out.println("子线程结束"); }); worker.start(); worker.join(); // 主线程在此阻塞,直到worker结束[^1] System.out.println("主线程继续执行"); ``` **输出**: ``` 子线程开始工作 (等待2秒) 子线程结束 主线程继续执行 ``` ##### 2. 等待多个线程 ```java List<Thread> threads = new ArrayList<>(); for (int i = 0; i < 3; i++) { Thread t = new Thread(/* 任务逻辑 */); t.start(); threads.add(t); } // 主线程等待所有子线程结束 for (Thread t : threads) { t.join(); // 依次等待每个线程[^3] } System.out.println("所有子线程已完成"); ``` ##### 3. 带超时控制 ```java Thread worker = new Thread(/* 长时间任务 */); worker.start(); worker.join(3000); // 最多等待3秒[^5] if (worker.isAlive()) { System.out.println("超时,强制中断"); worker.interrupt(); // 中断仍在运行的线程 } ``` #### 四、应用场景 1. **结果依赖** 主线程需要子线程计算结果时: ```java class ResultHolder { int result; } ResultHolder holder = new ResultHolder(); Thread calculator = new Thread(() -> { holder.result = compute(); // 耗时计算 }); calculator.start(); calculator.join(); // 确保结果已计算 System.out.println("结果:" + holder.result); // [^4] ``` 2. **资源有序释放** 确保子线程释放资源后主线程继续: ```java Thread fileProcessor = new Thread(() -> { try (FileInputStream fis = new FileInputStream("data.txt")) { // 处理文件 } }); fileProcessor.start(); fileProcessor.join(); // 确保文件已关闭 Files.delete(Paths.get("data.txt")); // 安全删除 ``` 3. **任务分阶段执行** 多阶段任务控制: ```java // 第一阶段:数据加载 Thread dataLoader = new Thread(loadDataTask); dataLoader.start(); dataLoader.join(); // 第二阶段:数据处理(依赖已加载数据) Thread dataProcessor = new Thread(processDataTask); dataProcessor.start(); ``` #### 五、注意事项 1. **中断处理** 若等待中被中断,会抛出 `InterruptedException`: ```java try { worker.join(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 恢复中断状态 // 清理逻辑 } ``` 2. **锁机制** `join()` 使用 `synchronized` 关键字,但**不会释放调用线程持有的其他锁**(仅获取目标线程对象的锁)[^2]。 3. **性能替代方案** 对于复杂同步需求,优先使用 `java.util.concurrent` 工具: ```java // 使用CountDownLatch替代join CountDownLatch latch = new CountDownLatch(3); ExecutorService executor = Executors.newFixedThreadPool(3); for (int i = 0; i < 3; i++) { executor.submit(() -> { try { /* 任务 */ } finally { latch.countDown(); } }); } latch.await(); // 等待所有任务完成 executor.shutdown(); ``` > **最佳实践**: > - 优先使用线程池(`ExecutorService`)管理线程 > - 需要简单等待时用 `join()`,复杂同步用 `CountDownLatch/CyclicBarrier` > - 始终处理 `InterruptedException`
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值