当我们频繁创建删除大量对象的时候,对象的创建删除所造成的开销就不容小觑了。为了提高性能,我们往往需要实现一个对象池作为Cache:使用对象时,它从池中提取。用完对象时,它放回池中。从而减少创建对象的开销。
由于.net BCL库中并没有对象池的标准实现,因此需要我们自己去实现。好在实现功能简单的对象池并不麻烦,一般几十行代码就能实现。需要注意的一点是,对象池大多是需要支持多线程访问的,因此需要考虑线程安全问题。
在.Net 4.0后,BCL在System.Collections.Concurrent名字空间下引入了一系列线程安全的对象,微软甚至在MSDN上给了一个通过实现对象池的简单示例:How to: Create an Object Pool by Using a ConcurrentBag.
这个例子本身没有什么问题,但如果放在实际生产中就觉得有点简单过头了,一般还需要加上容量限制和重用时进行reset操作。这里我就稍微将其改了下:
1 public class ObjectPool<T> 2 { 3 ConcurrentBag<T> buffer; 4 Func<T> createFunc; 5 Action<T> resetFunc; 6 7 public ObjectPool(Func<T> createFunc, Action<T> resetFunc, int capacity) 8 { 9 Contract.Assume(createFunc != null); 10 Contract.Assume(capacity > 0); 11 12 this.buffer = new ConcurrentBag<T>(); 13 this.createFunc = createFunc; 14 this.resetFunc = resetFunc; 15 16 this.Capacity = capacity; 17 } 18 19 public int Capacity { get; private set; } 20 public int Count { get { return buffer.Count; } } 21 22 /// <summary> 23 /// 申请对象 24 /// </summary> 25 public T GetObject() 26 { 27 var obj = default(T); 28 29 if (!buffer.TryTake(out obj)) 30 return createFunc(); 31 else 32 return obj; 33 } 34 35 /// <summary> 36 /// 释放对象 37 /// </summary> 38 public void PutObject(T obj) 39 { 40 Contract.Assume(obj != null); 41 42 if (Count >= Capacity) //超过容量了,不再需要 43 return; 44 45 if (resetFunc != null) 46 resetFunc(obj); 47 48 buffer.Add(obj); 49 } 50 }
需要注意的是,我这里的实现并没有完全确保Capacity的绝对性:当两个线程同时往一个即将到达上限的对象池中放置对象时,可能都会成功。因为我觉得这个是没有太大必要的,感兴趣的朋友可以把它改下。