一个项目的一个功能点,需要从接口接受返回数据,并对返回的数据进行一些业务处理,处理完成之后,添加到一个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. css3新特性@media(媒体查询)

    现在web页面运行的设备的屏幕从pc端到pad,从pad到手机,各种各样,这样就用到了现在经常用到的响应式页面,为实现响应式页面开发,就用到一个非常有用的css3属性media(媒体查询). 介绍:媒 ...

  2. JDK、JRE、JVM三者间的关系

    JDK(Java Development Kit)是针对Java开发员的产品,是整个Java的核心,包括了Java运行环境JRE.Java工具和Java基础类库.Java Runtime Enviro ...

  3. getElementsByTagName获得的不是数组的问题!

    getElementsByTag() returns a NodeList instead of an Array. You can convert a NodeList to an Array bu ...

  4. 如何让你的UWP应用程序无缝调用几何作图

    有时候需要编辑一些几何图形,如三角形,圆锥曲线等,在UWP应用中加入这些几何作图功能是件费时间又很难做好的事.其实Windows 10 应用商店中已有一些专业的几何作图工具了,那么能借来一用吗?答案是 ...

  5. Windows下 maven3.3.1的安装步骤+maven配置本地仓库

    简单讲下maven的安装步骤: 1.在安装maven之前,先确保已经安装JDK1.6及以上版本,并且配置好环境变量. 2.下载maven3,最新版本是Maven3.3.1 ,下载地址:http://m ...

  6. 17.iOS App设置icon,启动图,App名称的方法

    icon:选择Assets-->AppIcon-->将各种尺寸的icon拖拽到相应的框中. APP名称:选择info-->Bundle name,修改APP名字. 启动图: 首先点击 ...

  7. virtualbox下面安装ubuntu后外网如何远程ssh访问

    这两天在折腾virtualbox安装linux的事情,想多弄几个节点,装hadoop, 环境如下 两台thinkpad, 一台正常上班用的,win7 一台装的ubuntu kylin 16.04, 上 ...

  8. apache安装mod_deflate配置支持gzip

    apache 配置支持gzip apache使用gzip压缩能够大幅提高网站访问速度并节省网络流量,在网页响应头信息中可以判断是否支持压缩. HTTP/1.1 200 OK Date: Wed, 14 ...

  9. Java Environment Setting

    As a non-Java developer, I am quit stuck in Java environment setting because I am not familiar with ...

  10. java解析json

    1:下载另外一个Java的小包就可以了: http://www.JSON.org/java/json_simple.zip 里面有源码和文档例题和编程的lib包:编程只需要json_simple.ja ...