一个 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 计算表达式可以执行输入的代码,查看表达式的值,调用系统函数等。

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

相关推荐
介si啥呀~32 分钟前
解决splice改变原数组的BUG(拷贝数据)
java·前端·bug
Code哈哈笑32 分钟前
Idea连接远程云服务器上的MySQL,开放云服务器端口
服务器·后端·mysql·spring
奋进的小暄4 小时前
数据结构(java)栈与队列
java·开发语言·数据结构
SnXJi_4 小时前
纷析云开源财务软件:企业财务数字化转型的灵活解决方案
java·gitee·开源·开源软件
pingzhuyan4 小时前
03(总)-docker篇 Dockerfile镜像制作(jdk,jar)与jar包制作成docker容器方式
java·docker·jar
caihuayuan45 小时前
Redis奇幻之旅(三)1.redis客户端与服务端
java·大数据·sql·spring·课程设计
匆匆整棹还6 小时前
关于tomcat乱码和idea中控制台乱码的问题
java·tomcat·intellij-idea
何似在人间5757 小时前
SpringAI+DeepSeek大模型应用开发——1 AI概述
java·人工智能·spring·springai
匹马夕阳7 小时前
Java开发中的设计模式之观察者模式详细讲解
java·观察者模式·设计模式
风铃儿~7 小时前
Java微服务注册中心深度解析:环境隔离、分级模型与Eureka/Nacos对比
java·分布式·微服务·面试