一个项目的一个功能点,需要从接口接受返回数据,并对返回的数据进行一些业务处理,处理完成之后,添加到一个List<T>中,然后在View中循环这个List<T>,展示所有的数据。每次从接口中取回的数据量不等,最多会有上百条。虽说上百条也不算多,但是每条数据都要经过一系列的业务处理,感觉这样也挺耗时的,于是考虑使用Parallel.Foreach来进行并行处理。

项目完成之后,对比了一下并行和非并行的情况,发现并行之后并没有提高多少效能,倒是遇到了一些比较怪异的问题。

Parallel.Foreach 中对List<T>执行Add操作之后,List<T>的Count有时候并不是执行并行的操作的执行次数,而且List<T>中会有Item为null的情况。其实这个问题的解决方案很简单,就是因为List<T>不是线程安全的类,在多线程情况下就会导致一些不可预知的情况,加个锁就可以解决问题了。但是,如果能更好的了解到底是什么原因导致的,岂不更好,于是在一个同事的帮助下,找到了List<T>的源码(感谢微软开放了源码,地址:http://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,cf7f4095e4de7646),从List<T>的源码中更进一步的了解了导致以上问题的原因。

在分析上面两个问题之前,我们先了解一下List<T>的内部情况。从源码中我们可以看到List<T>是通过一个Array来进行处理的,如果初始没有对List<T>设置容量,List<T>容量将为0,如果此时使用Add添加新项的时候,就会给List<T>设置一个初始容量(初始值为4)。使用Add添加新项的时候,如果已经达到容量最大值,List<T>会自动扩充容量的值,扩充后的容量的值为原来既有项目数量的2倍(其实也就是原来容量的2倍)。

我们把Add方法和扩容方法摘抄如下:

public void Add(T item) {
if (_size == _items.Length) EnsureCapacity(_size + );
_items[_size++] = item;
_version++;
}
private void EnsureCapacity(int min) {
if (_items.Length < min) {
int newCapacity = _items.Length == ? _defaultCapacity : _items.Length * ;
// Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow.
// Note that this check works even when _items.Length overflowed thanks to the (uint) cast
if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength;
if (newCapacity < min) newCapacity = min;
Capacity = newCapacity;
}
}

了解了List<T>内部内部扩容情况之后,下面就以上两个问题进行分析。

1、 List<T>中的Item数量比预期的少。

导致这个问题的原因其实还是挺明显的。当两个线程(ThreadA和TreadB),同时调用Add方法添加不同的值的时候,如果此时ThreadA和ThreadB获取到的size相同,就会出现下面这种情况:

ThreadA:List<T>[size] = A;

ThreadB:List<T>[size] = B;

这种情况下,在size这个位置只会有一个ThreadB设置的值,ThreadA设置的值将会被替换掉,这也就是造成Item数量比预期少的原因。

2、 List<T>中的Item有null。

其实和上面类似,看Add中的代码:

_items[_size++] = item;

我们改变一下,变成:

(1)_size = _size+1;

(2)Items[_size] = item;

如果ThreadA执行完(1)之后ThreadB获取到新的_size也执行了(1)那此时_size就相当于是加2了,所以_size+1索引位置的项就是T的默认值了(值类型会值类型的默认值,引用类型为null)。这样就能解释为什么会出现null的原因了。

其实这两个问题完全就是同一个问题,只不过表象不同而已。最终解决方案很简单,要么自己加锁,要么使用线程安全的ConcurrentBag<T>。

对非线程安全类List<T>的一些总结的更多相关文章

  1. Java线程:线程安全类和Callable与Future(有返回值的线程)

    一.线程安全类 当一个类已经很好的同步以保护它的数据时,这个类就称为线程安全的.当一个集合是安全的,有两个线程在操作同一个集合对象,当第一个线程查询集合非空后,删除集合中所有元素的时候,第二个线程也来 ...

  2. Netty的并发编程实践4:线程安全类的应用

    在JDK1.5的发行版本中,Java平台新增了java.util.concurrent,这个包中提供了一系列的线程安全集合.容器和线程池,利用这些新的线程安全类可以极大地降低Java多线程编程的难度, ...

  3. 那些年读过的书《Java并发编程实战》二、如何设计线程安全类

    1.设计线程安全类的过程 设计线程安全类的过程就是设计对象状态并发访问下线程间的协同机制(在不破坏对象状态变量的不变性条件的前提下). (1)构建线程安全类的三个基本要素: 1)找出构成对象状态的所有 ...

  4. 非线程安全的HashMap 和 线程安全的ConcurrentHashMap

    在平时开发中,我们经常采用HashMap来作为本地缓存的一种实现方式,将一些如系统变量等数据量比较少的参数保存在HashMap中,并将其作为单例类的一个属性.在系统运行中,使用到这些缓存数据,都可以直 ...

  5. 【转】php Thread Safe(线程安全)和None Thread Safe(NTS,非 线程安全)之分

    Windows版的PHP从版本5.2.1开始有Thread Safe(线程安全)和None Thread Safe(NTS,非线程安全)之分,这两者不同在于何处?到底应该用哪种?这里做一个简单的介绍. ...

  6. JAVA中的线程安全与非线程安全

    原文:http://blog.csdn.net/xiao__gui/article/details/8934832 ArrayList和Vector有什么区别?HashMap和HashTable有什么 ...

  7. StringBuffer(线程安全)StringBuilder(非线程安全)

    StringBuffer属于线程安全,相对为重量级 StringBuilder属于非线程安全,相对为轻量级 线程安全的概念: 网络编程中许多线程可能会同时运行一段代码.当每次运行结果和单独线程运行的结 ...

  8. PHP 线程安全与非线程安全版本的区别深入解析

    Windows版的PHP从版本5.2.1开始有Thread Safe(线程安全)和None Thread Safe(NTS,非线程安全)之分,这两者不同在于何处?到底应该用哪种?这里做一个简单的介绍 ...

  9. Thread Safe(线程安全)和None Thread Safe(NTS,非线程安全)之分

    Windows版的PHP从版本5.2.1开始有Thread Safe(线程安全)和None Thread Safe(NTS,非线程安全)之分,这两者不同在于何处?到底应该用哪种?这里做一个简单的介绍. ...

随机推荐

  1. Codeforces 740A. Alyona and copybooks 模拟

    A. Alyona and copybooks time limit per test: 1 second memory limit per test: 256 megabytes input: st ...

  2. 编译安装或者mysql启动时遇到的错误小记

    编译安装遇到的错误:进入mysql目录 [root@localhost software]# cd mysql-5.6.19 [root@localhost mysql-5.5.11]# cmake ...

  3. jquery 给input赋值错误写法

    <script type="text/javascript"> var ue = UE.getEditor('container'); function getCont ...

  4. jenkins自动部署maven工程到服务器----SSH+shell

    今天心情不是很美丽,玩笑话可能没那么多,还是回归正题 1.指定SSH端口.用户名.密码相关配置,我这里没有需要配置密钥啥的. 2.接下来再创建任务的时候,进行SSH配置: 3.看到这里,是不是很想打我 ...

  5. eclipse中java项目的build path详解

    BuildPath中只支持加入jar文件,具体方法如下:在eclips里在工程名上右键->build path->contigure bud path->java build pat ...

  6. Java多线程基本概念

    基本概念 线程与任务的概念不一样. 任务:通常是一些抽象的且离散的工作单元,比如在Web请求中,针对用户的请求需要返回相应的页面是一个任务,在Java中实现Runnable接口的类也是一个任务. 线程 ...

  7. Twitter Bootstrap

    Twitter Bootstrap是一个HTML/CSS/JS框架,适用于移动设备优先的响应式网页开发.主要涉及: HTML:为已有的H5标签扩展了自定义属性 data-* CSS : Reset + ...

  8. python基础第四天(1)

    冒泡算法--算法 需求:请按照从小到大对列表 [13, 22, 6, 99, 11] 进行排序 思路:相邻两个值进行比较,将较大的值放在右侧,依次比较! 第一步 li = [13, 22, 6, 99 ...

  9. OD使用教程12

    载入程序输入关键字: 双击进入程序 仔细看发现并没有跳转直接跳到这个mov,往上看发现retn上面有一个push,在这种编写手法当中这种组合相当于一个jmp, 跳到离它最近的一个值(在这就是004A5 ...

  10. Android LayoutInflater原理分析,带你一步步深入了解View(一)

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/12921889 有不少朋友跟我反应,都希望我可以写一篇关于View的文章,讲一讲Vi ...