一个 IDEA 老鸟的 DEBUG 私货之多线程调试

总结 IDEA 好用的技巧,本篇介绍多线程的调试。

IntelliJ IDEA 2024

一、IDEA 多线程调试要点

多线程调试,需要先掌握以下两个核心要点。

要点一:查看运行栈帧 && 切换线程

在 Threads & Variables 这个窗口,进行线程之间切换。

要点二:断点暂停方式,选择 Thread

这个是最为重要的。

建议多线程调试:选择 Make Default,点击图中 Make Default,后续所有断点都是 Thread,如果不选择 Thread,则无法进行线程断点追踪!所有线程将直接运行结束。

二、 多线程调试案例

本篇以 AQS 为案例,讲解多线程的调试过程。

  • ABC 三个线程,共同争夺一把锁
  • 获得锁后执行 count++
  • 完成后释放锁

具体代码如下:可以拷贝到自己的 IDEA 中进行体验

Java 复制代码
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
    // ABC 三个线程抢夺一把锁。显示指明使用非公平锁
    private static final ReentrantLock lock = new ReentrantLock(false);
    // 获取锁后对 count 进行++ 操作
    private static volatile int count = 0;
    public static void main(String[] args) throws InterruptedException {
        // 线程 A 
        Thread a = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                // 获取锁
                lock.lock();
                try {
                    count++;
                    System.out.println(Thread.currentThread().getName() + " incremented count to " + count);
                } finally {
                    // 释放锁
                    lock.unlock();
                }
            }
        }, "A");
        // 线程 B 
        Thread b = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                // 抢占锁
                lock.lock();
                try {
                    count++;
                    System.out.println(Thread.currentThread().getName() + " incremented count to " + count);
                } finally {
                    lock.unlock();
                }
            }
        }, "B");
        // 线程 B 
        Thread c = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                // 抢占锁
                lock.lock();
                try {
                    count++;
                    System.out.println(Thread.currentThread().getName() + " incremented count to " + count);
                } finally {
                    lock.unlock();
                }
            }
        }, "C");

        a.start();
        
        // 先让 B 线程晚一点执行
        System.out.println("---------");
        Thread.sleep(20000);
        b.start();

        // C 线程最后执行
        System.out.println("---------");
        Thread.sleep(20000);
        c.start();

        a.join();
        b.join();
        c.join();
    }
}

模拟 ABC 三个线程抢锁过程,分别是 A 先抢到锁,然后 B、 C 再进入抢锁。 即三个线程运行。

2.1 模拟线程 A 执行

注意在 B、C 开始的位置设置断点,这样能够控制 B、C 线程的启动时间。

通过栈帧切换,就能执行线程 A 的所有相关逻辑了。

2.2 模拟线程 B 执行

先切换到 main 线程,启动线程 B; 当线程 B 启动后,切换到线程 B, 断点调试执行线程 B 的抢占过程。

步骤一:切换 main 线程,让线程 B 运行。

步骤二:当线程 B 运行成功后,切换线程 B,进行断点追踪。

当切换到 B 线程后,就能在 B 线程进行调试了。C 线程的逻辑类似。

最后,可以通过这种方式,查看 AQS 的锁队列情况。比如执行了两个线程后, AQS 中的同步队列器的状态如下所示。

细节可以自行探索, 本文不过多介绍了。

IDEA 在 Debug 时默认阻塞级别是 all,会阻塞其它线程,只有在当前调试线程走完时才会走其它线程; Thread 模式在 remote 调试时不阻塞他人请求。

三、小结

  1. 可以配合条件断点和日志来缩小范围,提高调试效率。
  2. 打印线程名称等方式来掌握各个线程状态, 多线程下日志的打印也是一个非常好的方式。
  3. evaluate Expression 计算表达式可以执行输入的代码,查看表达式的值,调用系统函数等。

调试多线程程序是一个复杂且需要耐心和技巧的任务,通过不断实践和学习,从而更好地应对多线程程序的挑战。

相关推荐
沉静的小伙6 分钟前
在微服务中使用领域事件
java·运维·微服务
GoGeekBaird12 分钟前
我最近在写 BeeWeave,想把 Agent 用过的上下文留住
后端·github·ai编程
醇氧17 分钟前
Spring 容器 Map 注入机制详解
java·后端·spring
梨子同志19 分钟前
MyBatis-Plus
后端
doiito28 分钟前
微服务资源优化新思路:Sablier + Podman 实现按需启动与自动休眠
后端·微服务·性能优化
接着奏乐接着舞。42 分钟前
【2026年7月最新】69道RAG面试题
前端·人工智能·后端·aigc·embedding·rag
栈溢出了1 小时前
Java Lambda 表达式笔记
java·开发语言·intellij-idea
会周易的程序员1 小时前
使用 pybind11 封装 C++ 日志库 microLog 为 Python 模块
java·c++·python·物联网·架构·日志·aiot
edwarddamon1 小时前
Spring Cloud Gateway 全链路 traceId 传递方案
后端