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

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

相关推荐
星星在线9 小时前
MusicFree:一个「All in One」的个人音乐服务器,让听歌回归简单
前端·后端
IT_陈寒10 小时前
Redis的SETNX并发问题让我加了三天班
前端·人工智能·后端
demo007x10 小时前
Docling 文档转换以及技术架构分析
前端·后端·程序员
NE_STOP11 小时前
Vide Coding--AI编程工具的选择
java
袋鱼不重11 小时前
我的神奇同事,AI 用多了居然写了个 Open In Codex
前端·后端·ai编程
用户83562907805111 小时前
使用 Python 操作 Word 内容控件
后端·python
像我这样帅的人丶你还11 小时前
啥? 前端也要会干Java?🛵🛵🛵
后端
Hommy8811 小时前
【剪映小助手】添加贴纸接口(Add Sticker)
后端·github·剪映小助手·视频剪辑自动化·剪映api
码云数智-园园12 小时前
C++20 Modules 模块详解
java·开发语言·spring
程序员黑豆12 小时前
JDK 下载安装与配置详细教程
java·前端·ai编程