最近项目涉及到大量数据并发的问题,短时间内会产生和销毁大量对象,因此担心会不会影响系统的性能,考虑使用对象池的方案。
但是在对对象池的学习中,对于是否使用对象池产生的疑问。
MSDN中 “How to: Create an Object Pool by Using a ConcurrentBag”(https://msdn.microsoft.com/en-us/library/ff458671.aspx)一文中提到
“Object pools can improve application performance in situations where you require multiple instances of a class and the class is expensive
to create or destroy. ”,这里考虑是否必须当类实例的创建和销毁开销比较大的时候才能够使用对象池?如果对象创建的太频繁会不会也会造成很大
的开销,影响系统的性能,同时对象的销毁也是个问题。因为对GC了解不够深入,不敢妄下判断。
在一种轻量级对象池的设计与实现文中提到什么时候不要池化:
采用对象池化的本意,是要通过减少对象生成的次数,减少花在对象初始化上面的开销,从而提高整体的性能。然而池化处理本身也要付出代价,因此,并非任何情况下都适合
采用对象池化。Dr. CliffClick在JavaOne 2003上发表的《Performance Myths Exposed》中,给出了一组其它条件都相同时,使用与不使用对象池化技术的实际性能的比较结果。
他的实测结果表明:对于类似Point这样的轻量级对象,进行池化处理后,性能反而下降,因此不宜池化;对于类似Hashtable这样的中量级对象,进行池化处理后,性能基本不变,
一般不必池化(池化会使代码变复杂,增大维护的难度);对于类似JPanel这样的重量级对象,进行池化处理后,性能有所上升,可以考虑池化。根据使用方法的不同,实际的
情况可能与这一测量结果略有出入。在配置较高的机器和技术较强的虚拟机上,不宜池化的对象的范围可能会更大。不过,对于像网络和数据库连接这类重量级的对象来说,
目前还是有池化的必要。基本上,只在重复生成某种对象的操作成为影响性能的关键因素的时候,才适合进行对象池化。如果进行池化所能带来的性能提高并不重要的话,
还是不采用对象池化技术,以保持代码的简明,而使用更好的硬件和更棒的虚拟机来提高性能为佳。
随后,在一篇讲解GC知识的文章:垃圾回收机制GC知识再总结兼谈如何用好GC中提到“不要频繁的new生命周期很短object,这样频繁垃圾回收频繁压缩
有可能会导致很多内存碎片,可以使用设计良好稳定运行的对象池(ObjectPool)技术来规避这种问题”,看来对于自己项目的情况是否可以使用对象池
还有待项目测试后再做判断。
这里先总结可能使用对象池的两种情况(可能不完全正确):
- 需要多个类的实例,当类实例的创建和销毁开销比较大;
- 对象生命周期很短,频繁的进行创建和销毁。 对象最好是无状态的,否则重复使用前回复原始状态;
了解了以上知识后,在网上找到了几个实现方案,大体思路是一致的:
1. 编写高效的C++程序方法之使用对象池: C++的实现方法,思路简单明晰,值的借鉴,但方法在多线程中使用感觉会有问题。
2. 在C#中实现简单的对象池 :此方案利用ConcurrentBag<T>类,实现了对象池的功能。因为本人开发环境为C#,因此考虑使用此方法。
这里面ConcurrentBag<T>类是头一次使用,MSDN中的解释是:
ConcurrentBag<T> is used to store the objects because it supports fast insertion and removal, especially when the same thread is both
adding and removing items.
Bags are useful for storing objects when ordering doesn‘t matter, and unlike sets, bags support duplicates.
ConcurrentBag<T> is a thread-safe bag implementation, optimized for
scenarios where the same thread will be both producing and consuming data stored in the bag.
3. 一种轻量级对象池的设计与实现: java的实现方式,值的借鉴;
4. 一个通用并发对象池的实现:http://ifeve.com/generic-concurrent-object-pool/,同样是java;
5. Lock Free ObjectPool的C#实现 (对象池):http://www.360doc.com/content/14/1015/09/5054188_417089038.shtml;
6. C#对象池详细解析。
学习了以上的方案,确定了自己要使用的方案。声明:本方案不是原创。
1 /// <summary> 2 /// 对象池 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 public class ObjectPool<T> 6 { 7 private readonly ConcurrentBag<T> _buffer; 8 9 private readonly Func<T> _createFunc; 10 private readonly Action<T> _resetFunc; 11 12 public int Capacity { get; private set; } 13 14 private readonly int _chunkSize; 15 public int Count { get { return _buffer.Count; } } 16 17 public ObjectPool(Func<T> createFunc, Action<T> resetFunc, int capacity = 50, int chunkSize = 10) 18 { 19 if (createFunc == null) 20 { 21 throw new ArgumentNullException("createFunc"); 22 } 23 if (capacity <= 0) 24 { 25 throw new ArgumentOutOfRangeException("capacity"); 26 } 27 if (chunkSize <= 0) 28 { 29 throw new ArgumentOutOfRangeException("chunkSize"); 30 } 31 32 this._buffer = new ConcurrentBag<T>(); 33 this._createFunc = createFunc; 34 this._resetFunc = resetFunc; 35 36 this.Capacity = capacity; 37 this._chunkSize = chunkSize; 38 39 AllocateChunk(); 40 } 41 42 public T GetObject() 43 { 44 T obj; 45 if (!_buffer.TryTake(out obj)) 46 { 47 //创建一些数据 48 AllocateChunk(); 49 return _buffer.FirstOrDefault(); 50 } 51 return obj; 52 } 53 54 public void ReleaseObject(T obj) 55 { 56 Contract.Assume(obj != null); 57 58 //超过容量了,不再需要 59 if (Count >= Capacity) return; 60 61 if (_resetFunc != null) 62 { 63 _resetFunc(obj); 64 } 65 66 _buffer.Add(obj); 67 } 68 69 private void AllocateChunk() 70 { 71 for (int i = 0; i < _chunkSize; i++) 72 { 73 _buffer.Add(_createFunc()); 74 } 75 } 76 }
下面贴出MSDN中对象池的使用方法:
1 class Program 2 { 3 staticvoid Main(string[] args) 4 { 5 CancellationTokenSource cts = new CancellationTokenSource(); 6 7 // Create an opportunity for the user to cancel. 8 Task.Run(() => 9 { 10 if (Console.ReadKey().KeyChar == ‘c‘ || Console.ReadKey().KeyChar == ‘C‘) 11 cts.Cancel(); 12 }); 13 14 ObjectPool<MyClass> pool = new ObjectPool<MyClass> (() => new MyClass()); 15 16 // Create a high demand for MyClass objects. 17 Parallel.For(0, 1000000, (i, loopState) => 18 { 19 MyClass mc = pool.GetObject(); 20 Console.CursorLeft = 0; 21 // This is the bottleneck in our application. All threads in this loop// must serialize their access to the static Console class. 22 Console.WriteLine("{0:####.####}", mc.GetValue(i)); 23 24 pool.PutObject(mc); 25 if (cts.Token.IsCancellationRequested) 26 loopState.Stop(); 27 28 }); 29 Console.WriteLine("Press the Enter key to exit."); 30 Console.ReadLine(); 31 cts.Dispose(); 32 } 33 }