总结 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 调试时不阻塞他人请求。
三、小结
- 可以配合条件断点和日志来缩小范围,提高调试效率。
- 打印线程名称等方式来掌握各个线程状态, 多线程下日志的打印也是一个非常好的方式。
- evaluate Expression 计算表达式可以执行输入的代码,查看表达式的值,调用系统函数等。
调试多线程程序是一个复杂且需要耐心和技巧的任务,通过不断实践和学习,从而更好地应对多线程程序的挑战。