You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JUnit测试Runnable线程时循环仅执行一次的问题排查与解决

JUnit测试中多线程Sleep后循环仅执行一次的问题解析与修复

问题描述

我在学习Java多线程时,将参考的Demo代码迁移到JUnit测试中实现(原本是独立Java程序),遇到了一个奇怪的问题:

我定义了两个实现Runnable接口的类,它们的run方法里都有一个循环,期望执行5次,每次间隔0.5秒(用Thread.currentThread().sleep(500)实现)。但实际运行测试时,循环逻辑只执行了一次;移除sleep语句后,循环能正常执行5次。

测试代码如下:

import org.junit.jupiter.api.Test;
public class TheadTest {
    @Test
    void Threadtest2() {
        Hi hi = new Hi();
        Hello hello = new Hello();
        Thread t1 = new Thread(hi);
        Thread t2 = new Thread(hello);
        t1.start();
        t2.start();
    }
}
class Hi implements Runnable {
    public void runx() {}
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("HI");
            System.out.println(i);
            try {
                Thread.currentThread().sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class Hello implements Runnable {
    public void runx() {}
    public void run() {
        for (int j = 0; j < 5; j++) {
            System.out.println("Hello");
            try {
                System.out.println(j);
                Thread.currentThread().sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

问题原因

核心问题出在JUnit测试的生命周期机制上:

  • JUnit测试方法(这里的Threadtest2)是在一个主线程中执行的,当这个方法执行完最后一行代码(t2.start())后,主线程就会结束。
  • JUnit测试框架默认不会等待测试方法中启动的后台线程完成,一旦测试方法执行完毕,测试进程就会开始终止流程,JVM会强制终止所有还在运行的非守护线程(你的t1和t2都是非守护线程)。
  • 当你添加sleep(500)后,子线程刚执行完第一次循环就进入休眠状态,此时测试主线程已经结束,测试进程随之终止,子线程还没来得及执行剩下的4次循环就被杀死了;而移除sleep时,子线程的循环执行速度极快,在测试进程终止前就完成了5次循环,所以看起来正常。

另外补充一点:Thread.currentThread().sleep(500)和Thread.sleep(500)效果完全一样,因为sleep是静态方法,无论用哪个线程对象调用,都是让当前执行的线程休眠,这里写法可以简化为Thread.sleep(500)。

修复方法

解决思路很简单:让测试主线程等待两个子线程全部执行完成后再结束,常用的方式有两种:

方法1:使用Thread.join()

在测试方法中启动线程后,调用t1.join()和t2.join(),这会让测试主线程阻塞,直到对应的子线程执行完毕:

import org.junit.jupiter.api.Test;
public class TheadTest {
    @Test
    void Threadtest2() throws InterruptedException { // 需要声明抛出InterruptedException
        Hi hi = new Hi();
        Hello hello = new Hello();
        Thread t1 = new Thread(hi);
        Thread t2 = new Thread(hello);
        t1.start();
        t2.start();
        
        // 等待两个子线程完成
        t1.join();
        t2.join();
    }
}
// Hi和Hello类代码不变

方法2:使用CountDownLatch(更适合多线程场景扩展)

如果后续需要等待更多线程,CountDownLatch会更灵活:

import org.junit.jupiter.api.Test;
import java.util.concurrent.CountDownLatch;
public class TheadTest {
    @Test
    void Threadtest2() throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(2); // 计数器初始值为2,对应两个线程
        
        Hi hi = new Hi(latch);
        Hello hello = new Hello(latch);
        Thread t1 = new Thread(hi);
        Thread t2 = new Thread(hello);
        t1.start();
        t2.start();
        
        latch.await(); // 等待计数器归0
    }
}
class Hi implements Runnable {
    private CountDownLatch latch;
    public Hi(CountDownLatch latch) {
        this.latch = latch;
    }
    public void runx() {}
    public void run() {
        try {
            for (int i = 0; i < 5; i++) {
                System.out.println("HI");
                System.out.println(i);
                Thread.sleep(500);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            latch.countDown(); // 线程完成,计数器减1
        }
    }
}
class Hello implements Runnable {
    private CountDownLatch latch;
    public Hello(CountDownLatch latch) {
        this.latch = latch;
    }
    public void runx() {}
    public void run() {
        try {
            for (int j = 0; j < 5; j++) {
                System.out.println("Hello");
                System.out.println(j);
                Thread.sleep(500);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            latch.countDown(); // 线程完成,计数器减1
        }
    }
}

两种方法都能保证测试主线程等待子线程完成所有循环后再结束,这样sleep后的循环就能正常执行5次了。

内容的提问来源于stack exchange,提问作者RAY

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 14:52:52