集合是我们经常用的技术。但C#集合在默认情况下都不是线程同步的,之所以这样是因为不同步状态的集合将使应用程序保持最佳性能。当然如果你的集合对象在程序中的操作不是很多而且又存在多线程的访问,那么你可以使用Lock关键字锁定所有操作集合的代码段(包括增、删、改、查等任何集合操作)。比如:
- ArrayList ls = new ArrayList();
- 线程A:
- lock(ls) {......} //操作ArrayList里面的数据
- 线程B:
- lock(ls) {......} //操作ArrayList里面的数据
- 你也可以使用其他对象作为锁:
- ArrayList ls = new ArrayList();
- Object lockObj = new Object();
- 线程A:
- lock(lockObj) {......} //操作ArrayList里面的数据
- 线程B:
- lock(lockObj) {......} //操作ArrayList里面的数据
当然如果你像上面这样做了,我认为你是个很有耐心很愿意吃苦的人。因为程序里涉及到集合操作的代码往往非常多,你把每一块代码都锁定会大大影响程序的执行效率,即使你认为在你的程序里效率并不重要,那么你想过没有,对于团队开发,别人也可能会使用你的代码,难道他也一定和你一样有足够的耐心和细心来锁定每一块操作集合的地方么?一旦有的集合操作语句没有被锁定成为漏网之鱼,那么你就会体会到线程给你带来的灾难了(你的程序会莫名其妙的崩溃,并且很难找到原因)。所以说,全部锁定集合的操作代码是一种吃力不讨好的做法,我们不提倡这样做。
我们要做的是锁定集合对象或者干脆建立一个线程安全的集合对象(线程安全是指对象在操作时是线程同步的)。要做到这一点,我们需要以前在介绍ICollection接口时介绍的两个公共属性,它们提供了对集合线程同步处理的支持:
IsSynchronized,用于判断该集合是否是线程安全的。
SyncRoot,返回集合的锁定对象。
另外,你如果查看像ArrayList、Hashtable、Queue等各个集合类时你会发现几乎所有的集合都提供了一个静态方法Synchronized来创建一个集合对象的线程安全包装。
有了这两个属性和一个方法,我们就可以建立一套合理的线程安全的集合操作机制。首先你可以利用Synchronized方法返回一个线程安全的集合对象,比如:
- ArrayList ht =ArrayList.Syncronized(new ArrayList ());
对于线程安全的集合对象,你在线程操作时不需要锁定就可以任意操作了,比如:
- ArrayList ls = new ArrayList ();
- 线程A:
- ls.Add("宝马"); //添加ArrayList里面的数据
- 线程B:
- ls[0]="BMW" ; //修改ArrayList里面的数据
但是通过Synchronized返回的集合对象系统对它的每一个方法里都进行了安全处理(比如所有的方法都加了Lock关键字)。还是一个效率低的问题,设想一下,如果你的项目中用到的集合操作非常多(一般都很多),那你把项目中的所有集合对象都使用安全集合对象将会带来多大的效率损失?如果你不把项目中所有集合对象都变为线程安全的,那么非线程安全集合对象和线程安全集合对象一起使用该怎么办。总之不好办,代码将十分混乱,非安全集合对象和线程安全集合对象一起使用很容易造成对集合的双重锁定,这是会导致死锁的做法。所以Synchronized方法要慎重使用,一般项目中集合对象较少而且又需要多线程操作时使用Synchronized方法是最好的方案。
如果项目中的集合操作非常多,同时又要保证线程操作的安全性,最佳的方案是使用集合的SyncRoot属性。通常情况下,当前集合对象的SyncRoot属性值就是当前实例本身,当然,如果当前集合是一个安全集合那么返回的是它所包装集合的实例,比如:
- ArrayList ls = new ArrayList();
- ArrayList lsSafe = ArrayList.Synchronized(ls);
- lock(lsSafe. SyncRoot){......}
上面的lock(lsSafe. SyncRoot)实际上相当于lock(ls. SyncRoot),这就避免了对安全集合对象的锁定,也就是避免了双重锁定。这便于你使用安全集合对象和集合对象来混合编程,因为这样的确可以提高效率。对于IsSynchronized属性就是为了避免双重锁定,通过这个属性你可以来判断当前集合是否是安全集合对象,如果是就不需要再锁定了。
代码示例:
- using System;
- using System.Collections;
- using System.Threading;
- public class SamplesArrayList
- {
- //线程访问的集合
- static ArrayList myAL = new ArrayList();
- //线程访问的集合安全包装
- static ArrayList sfAL = ArrayList.Synchronized(myAL);
- public static void Main()
- {
- Thread T1 = new Thread(new ThreadStart(Write));
- T1.Start();
- Thread T2 = new Thread(new ThreadStart(Read));
- T2.Start();
- }
- public static void Write()
- {
- while (true)
- {
- //锁定集合进行操作
- lock (myAL.SyncRoot)
- {
- myAL.Add(DateTime.Now.ToString());
- }
- }
- }
- public static void Read()
- {
- while (true)
- {
- //锁定集合进行操作
- lock (sfAL.SyncRoot)
- {
- foreach (object obj in myAL)
- Console.WriteLine("读: {0} ", obj.ToString());
- }
- }
- }
- }
上面的lock (myAL.SyncRoot)如果替换成lock (myAL)或lock (sfAL.SyncRoot)将会达到同样的效果。锁定SyncRoot对于实现ICollection接口的集合类来讲它更具有一般性,因为所有集合类都对ICollection接口进行了重写。所以当你要对集合进行锁定访问时,假如你并不能确定集合对象和SyncRoot之间的关系(SyncRoot可能是集合本身,对于使用Synchronized产生的集合是其原始集合),那么还是锁定SyncRoot为最佳选择。另外使用SyncRoot还可以保持源代码的规范性。
转载于:https://blog.51cto.com/jianshusoft/643663