IDEA中的并发调试

本文详细介绍了如何在IDEA中进行并发调试,包括如何进入非主线程调试,设置挂起整个虚拟机,以及如何重现并发错误。通过实例演示了如何处理多线程操作ArrayList时可能出现的异常,如`ArrayIndexOutOfBoundsException`,并通过调整断点策略控制调试过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

IDEA中的并发调试

介绍

    
最近看葛一鸣了的"实战Java高并发程序设计"一书,里面有一章介绍了"并行程序调试",不过书中是基于Eclipse编辑器的,这里总结一下IDEA中的调试方法,大同小异。

    
实验样本如下:

/**
 * 两个线程都过了数组大小检查,先后插入数据时 引起 java.lang.ArrayIndexOutOfBoundsException
 */
public class UnsafeArrayList {
    static ArrayList al=new ArrayList();
    static class AddTask implements Runnable{
        @Override
        public void run() {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {}

            System.out.println( Thread.currentThread().getName() + " is run!");
            for(int i=0;i<1000000;i++){
                al.add(new Object());
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(new AddTask(),"t1");
        Thread t2=new Thread(new AddTask(),"t2");
        t1.start();
        t2.start();
        Thread t3=new Thread(new Runnable(){
            @Override
            public void run() {
                while(true){
                    try {
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {}
                    System.out.println( Thread.currentThread().getName() + " is run!");
                }
            }
        },"t3");
        t3.start();
    }
}

    
模拟多线程下操作ArrayList。t1和t2表示不同线程下操作同一个任务,t3是操作其他任务。

进入非主线程的调试

    
运行程序,抛出如下异常:

java.lang.ArrayIndexOutOfBoundsException: 28
	at java.util.ArrayList.add(ArrayList.java:463)
	at com.mm.mmblogs.b5.UnsafeArrayList$AddTask.run(UnsafeArrayList.java:17)
	at java.lang.Thread.run(Thread.java:748)

    
找到错误ArrayList.add里,得知错误在如下代码:

elementData[size++] = e;

    
将断点设置在ensureCapacityInternal方法上:

avatar

    
以调试方式启动程序,出现的Debugger图如下:

avatar

    
上图中,可看到主线程main停留在了ArrayList.add()函数断点处,显示了完整的调用堆栈。但很不幸的是,其实我们对主函数并没有太大兴趣。我们更关心的是程序中t1和t2线程对ArrayList的调用。

    
使用IDEA可以很好的实现这点,右击断点,条件中排除掉主线程main:

avatar

    
再次以调试方式启动程序,就可调试线程t1和t2了,出现的Debugger图如下:

avatar

    
上图中可看到t1和t2都在RUNNING状态,t3处于SLEEPING状态。但控制台中"t3 is run!"一直在不停的输出,说明t3线程并不会因为t1和t2断点而进入挂起模式。

    
当前选中的是t1线程,如果进行单步执行,t1会往下执行,而t2不会往下执行。如果要操作t2往下执行可以切换为t2线程。

挂起整个虚拟机

    
默认情况下,当断点条件成立时,系统会挂起相关的线程,没有断点的线程会继续执行。为了排除其他线程可能对整个调试产生不利的影响,我们可设置断点类型为挂起整个虚拟机,而不是挂起相关线程。操作如下:

avatar

    
选择"Suspend All"即可挂起整个虚拟机,如果想所有的端点模式都是挂起虚拟机而不是挂起相关线程,可设置为默认"Make Default"。

    
挂起整个虚拟机后,再次调试程序,这次t3线程并没有输出"t3 is run!",说明这次已经挂起整个虚拟机了。啥时候挂起相关线程,啥时候挂起整个虚拟机,需由实际场景决定。

重现错误

    
造成"java.lang.ArrayIndexOutOfBoundsException"的异常,很有可能在ArrayList容量快用完时(只有1个可用空间),如果两个线程同时进入add()函数,并同时判断认为系统满足继续添加元素而不需要扩容,那么两者都不会进行扩容操作。那么必然有一个线程会将数据写到边界外,从而产生ArrayArrayIndexOutOfBoundsException。

    
我们都知道,ArrayList默认容量是10。

private static final int DEFAULT_CAPACITY = 10;

    
进入add()函数后,模拟容量为9,断点设置如下:
![avatar][image6]

    
然后切换t1和t2两个线程进行扩容检查,下图为t1进行扩容检查,t2同理:
![avatar][image7]

    
接着,切换t1线程进行赋值成功,然后切换t2线程赋值,得到异常"java.lang.ArrayIndexOutOfBoundsException"。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值