一、常见的思维误区---不可能是这里出的问题

创建对象报异常了?要是放在以前我绝对会说这是扯***,但遇到这个实际问题后我才发现是自己浅薄了。我之前认为这个错误不可能是创建对象的时候报出来的,是因为我们认为代码是下面这样的,而且是单线程运行的,哪怕多执行几遍也是单线程运行的,这样当然不会出问题呀!那什么场景下创建对象会报错呢?请继续往下看。

public static void Main(string[] args)
    for (int i = 0; i < 20; i++)
        TestClass.TestArrayListSingleThread();
public class TestClass
	public static void TestArrayListSingleThread()
		Hashtable hashTable = new Hashtable();
		hashTable.Add("01", "01Value");
		hashTable.Add("02", "02Value");
		hashTable.Add("03", "03Value");
		ArrayList arrayList = new ArrayList(hashTable.Values);
		Console.WriteLine("hashTable.Count=" + hashTable.Count);

二、与常见逻辑相悖的多线程思维

实际上经过分析后,new ArrayList()报错的场景是下边这样的,把这个惊喜翻译出来就是:有61个线程在并发执行,其中60个线程在往hashTable中插入元素,1个线程利用hashTable创建ArrayList对象,这种并发程度报错的概率在90%以上。

public static void Main(string[] args)
    for (int i = 0; i < 20; i++)
        TestClass.TestArrayListMultiThread();
public class TestClass
	public static void TestArrayListMultiThread()
        Hashtable hashTable = new Hashtable();
        hashTable.Add("01", "01Value");
        hashTable.Add("02", "02Value");
        hashTable.Add("03", "03Value");
        //hashTable = Hashtable.Synchronized(hashTable);
        ParallelOptions options = new ParallelOptions();
        options.MaxDegreeOfParallelism = 100;
        Parallel.Invoke(options,
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        #region 29遍相同的代码
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },//10
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },//10
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },//10
                        #endregion
                            ArrayList arrayList = null;
                            //lock (hashTable.SyncRoot)
								arrayList = new ArrayList(hashTable.Values);
								Console.WriteLine("hashTable.Count=" + hashTable.Count);
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        #region 29遍相同的代码
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },//10
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },//10
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); },
                        () => { hashTable.Add("for" + Guid.NewGuid(), "forValue"); }//10
                        #endregion
        Console.WriteLine("执行完毕");

实际项目中也是单线程的写法,仅看代码我们会不自觉地陷入到单线程的思维中,因为一般定义和调用是分开的,不可能像我上边写的示例代码那样直接告诉你多线程的情况下会有问题,这需要我们自己总结分析,当单线程不会报错时跳出惯性思维,考虑一下会不会是异步、高并发导致了异常的发生,实际项目代码的具体截图如下:

现在只看自己写的代码已经找不出问题原因,那就去看源码吧,排查问题时首先要抱定一个信念"出了异常肯定有原因,只要深挖肯定能找到问题所在"。

三、源码分析

既然错误内容是“索引超出了数组界限”,那我们看源码的时候要重点关注数组相关的地方,下面从ArrayList的构造方法开始:

c.CopyTo(itemsToInsert, 0);中c是接口类型,要想继续跟下去需要知道c的实际类型,因为c是从new ArrayList(hashTable.Values)传过来的,实际上c就是hashTable.Values,我们只需要打断点看一下hashTable.Values的真实类型即可(System.Collections.Hashtable.ValueCollection) 

下面以buckets的长度=3、array的长度=2为例进行推演:
i=3、--i>=0为true-->执行array.SetValue(lbuckets[2].val, 0);//正常执行
i=2、--i>=0为true-->执行array.SetValue(lbuckets[1].val, 1);//正常执行
i=1、--i>=0为true-->执行array.SetValue(lbuckets[0].val, 2);//2超出了array的最大索引导致报错

四、为什么是c.CopyTo(itemsToInsert, 0);出问题了呢

其实我并没有一眼就看出问题出在这里,而是经过测试后发现这个方法的报错与实际环境中的报错向吻合,因此判断这里是问题点。下面是实际场景中记录的异常日志截图

 下面是测试SetValue()的异常截图,第二种情况与正式环境的异常相吻合,就这样找到了问题点

五、问题复现及解决方案

问题复现的代码就是【第二步】的代码,解决方法就是把代码中注释的内容放开。这个问题是用了挺长时间才分析出原因,一开始就没头绪,之后想到可以去看源码来找问题才慢慢找到了解决问题的途径,希望我的经历能对大家有所帮助,当真有用的话请点赞、评论、关注下,谢谢了。

创建对象报异常了?要是放在以前我绝对会说这是扯***,但遇到这个实际问题后我才发现是自己浅薄了。我之前认为这个错误不可能是创建对象的时候报出来的,是因为我们认为代码是下面这样的,而且是单线程运行的,哪怕多执行几遍也是单线程运行的,这样当然不会出问题呀!那什么场景下创建对象会报错呢?
对于http://www.cnblogs.com/lihaozy/archive/2010/10/28/1863469.html一文中的用索引器对某一对象中的内置数组访问时,会出现数组越界的情况,我们使用了ArrayList来代替该对象所属类中的数组。 using System; using System.Collections.Generic; using System.Linq...
今天遇到一个傻逼问题,保存一个数组时,出现索引超出数组界限; 原因:因为是封装好的DLL文件,没有仔细查看保存的数组内数据名称,导致在数组中查询不到那个数组中的数据名称,出现这个报错。 解决方法: 1:首先查询数组中的数据个数是否匹配; 2:查询你所匹配的数组中的数据名称是否存在。 问题解决。 转载于:https://www.cnblogs.com/Jack_G/ar...
//读取数据 StreamReader reader = new StreamReader(filename); //using streaming to read the file strline = reader.ReadToEnd(); reader.Close(); MessageBox.Show(“打开文件成功”); //处理数据 int begining = strline.IndexOf("END OF HEADER", 0); strline
线上数据库版本为SQL Server2012 R2,检查后发现开发人员SSMS版本为2008,版本与服务器不一致,(开发人员要求登录数据库服务器操作,果断拒绝了)建议在本地打上SP3或者直接安装2012的SSMS,安装好后问题解决
如果你的脚本出现了索引越界错误,那么可能是因为你在访问一个数组或列表的时候,指定的索引超过了它的最大下标(也就是索引从0开始的最大值)。 解决这个问题的方法是,检查你的代码,看看是否有地方使用了不合法的索引。如果发现了,则修改代码,使用合法的索引就可以了。 例如,如果你有一个长度为10的列表,那么合法的索引范围就是0到9。如果你试图访问列表的第10个元素,那么就会出现索引越界错误。 另外,还有一种...
```java // 创建一个长度为5的ArrayList<Integer>[]数组 ArrayList<Integer>[] arr = new ArrayList[5]; // 初始化每个元素为一个空的ArrayList<Integer> for (int i = 0; i < arr.length; i++) { arr[i] = new ArrayList<Integer>(); 需要注意的是,由于泛型在编译期擦除,因此无法直接创建泛型数组。在这种情况下,可以创建一个Object数组,然后进行强制类型转换。但是,这样会导致编译器发出警告,因此最好避免使用泛型数组。如果一定要使用泛型数组,可以使用@SupressWarnings注解来禁止警告。