一个项目的一个功能点,需要从接口接受返回数据,并对返回的数据进行一些业务处理,处理完成之后,添加到一个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. Unity3d 着色器语法(Shader)

    Shader "name" { [Properties] Subshaders [Fallback] } 定义了一个着色器.着色器拥有一个 Properties 的列表.着色器包含 ...

  2. NC nc5.x笔记(编辑中)

    一.设置卡片界面下 金额字段负数为红色! /** * 设置卡片界面下 金额字段负数为红色! */ private void repaintBodyMoneyColor(){ if(!isListPan ...

  3. MVC中使用EF:排序,过滤,分页

    原文链接:http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/sorting-filtering-and-paging ...

  4. UINavigationController push时,页面卡顿

    1.A push B A.view.backgroundColor 与 B.view.backgroundColor 不一致. 2. AssistiveTouch打开 关闭则不会出现卡顿情况 3.增加 ...

  5. 强大的Spring缓存技术(上)

    缓存是实际工作中非常常用的一种提高性能的方法, 我们会在许多场景下来使用缓存. 本文通过一个简单的例子进行展开,通过对比我们原来的自定义缓存和 spring 的基于注释的 cache 配置方法,展现了 ...

  6. Dreamweaver扩展注意事项

    对Dreamweaver扩展做了一些整理. 扩展开发扩展(Extension),是应用程序给用户预留的二次开发接口.Dreamweaver提供了对菜单,插入栏(Insertbar),浮动框等GUI部件 ...

  7. [PHP] Phalcon应用升级PHP7记录

    升级主要有 php7, phalcon, extension 这三个方面工作. [php7] 我选择从 git 中源码包安装,下面是主要安装步骤: git clone -b php- https:// ...

  8. Groovy 模版引擎

    1. Introduction Groovy supports multiple ways to generate text dynamically including GStrings, print ...

  9. AspNetPager 免费分页控件7.5.1版发布!

    AspNetPager 免费分页控件7.5.1版发布,本次升级主要内容有: 修正了ShowDisabledButtons为false时html闭合标签丢失的bug:改为从System.Web.UI.W ...

  10. 模拟XShell的小项目

    不知道大家有没有用过XShell这款工具,这款工具通过windows可以远程操作处于开机状态的linux操作系统,也就是说把你的电脑和一台服务器连入网络,你通过输入服务器所在的IP地址建立一个会话就可 ...