一个项目的一个功能点,需要从接口接受返回数据,并对返回的数据进行一些业务处理,处理完成之后,添加到一个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. 延迟对象$q和供应商配置config

    1.angular总的$q和jquery中的延迟对象很类似,用法也差不多 m1.controller('meng',['$scope','$q',function($scope,$q){ var df ...

  2. 深入理解Java虚拟机(一)、Java内存区域与内存溢出异常

    Java虚拟机所管理的内存包括以下几个运行时数据区: 程序计数器(PCR): 1.是一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器 2.为线程私有 3.执行Java方法有PCR,执行 ...

  3. 内核对TCP REUSEPORT的优化

    Q&A 当有人问起我关于reuseport的一些事的时候,我们的对话基本如下:Q1:什么是reuseport?A1:reuseport是一种套接字复用机制,它允许你将多个套接字bind在同一个 ...

  4. JAVA多线程 问题 转载

    参考:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-answers/ http://www.cn ...

  5. iOSDate时间格式(转)

    在开发iOS程序时,有时候需要将时间格式调整成自己希望的格式,这个时候我们可以用NSDateFormatter类来处理.例如: //实例化一个NSDateFormatter对象 NSDateForma ...

  6. dedecms内容管理系统学习

    在复习完基础知识和学习了tp3.2之后,我们开始学习一些开源的产品,如dedecms:

  7. VC++修改电脑系统时间

    我们可以通过代码,对系统时间进行获取和修改. 我将介绍两种方法.第一种,通过SYSTEMTIME 代码实现: #include <windows.h> #include <iostr ...

  8. Android 测试Handler的基本使用

    package com.sherlock.app_handler; import java.io.ByteArrayOutputStream; import java.io.InputStream; ...

  9. Fiddler 工作原理

    Fiddler工作原理: 就在在客户端与服务器端创建一个代理服务器: 在开启Fiddler后,Fiddler会自动窜改浏览器的代理,例如我们打开Fiddler,打开IE浏览器--设置--Interne ...

  10. python面向对象中的__init__方法怎么理解?

    我们在学习python类的时候,总会碰见书上的类中有__init__()这样一个函数,很多同学百思不得其解,其实它就是python的构造方法. 构造方法类似于类似init()这种初始化方法,来初始化新 ...