对象池就是对象的容器,旨在优化资源的使用,通过在一个容器中池化对象,并根据需要重复使用这些池化对象来满足性能上的需求。当一个对象被激活时,便被从池中取出。当对象被停用时,它又被放回池中,等待下一个请求。
对象池一般用于对象的初始化过程代价较大或使用频率较高的场景。
在 ASP.NET Core 框架里(或者.net Framework高版本)已经内置了一个对象池功能的实现:Microsoft.Extensions.ObjectPool
。
池的策略与基本使用
首先,要使用 ObjectPool
,需要创建一个池化策略,告诉对象池你将如何创建对象,以及如何归还对象。
该策略通过实现接口 IPooledObjectPolicy
来定义,下面是一个最简单的策略实现:
public class DefaultPooledObjectPolicy<T> : PooledObjectPolicy<T> where T : class, new() {/// <inheritdoc />public override T Create(){return new T();}/// <inheritdoc />public override bool Return(T obj){if (obj is IResettable resettable){return resettable.TryReset();}return true;} }
当对象池中没有实例时,则创建实例并返回给调用组件;当对象池中已有实例时,则直接取一个现有实例返回给调用组件。而且这个过程是线程安全的。
Microsoft.Extensions.ObjectPool
提供了默认的对象池实现:DefaultObjectPool<T>
,它提供了借 Get
和还 Return
操作接口。创建对象池时需要提供池化策略 IPooledObjectPolicy<T>
作为其构造参数。
下面是一个默认对象池的基本使用
public class AutoMan {public int Id { get; set; }public DateTime CreateTime { get; set; }public AutoMan(){Thread.Sleep(5000);//模拟对象耗时创建Id = new Random().Next();CreateTime = DateTime.Now;} }
var policy = new DefaultPooledObjectPolicy<AutoMan>(); var pool = new DefaultObjectPool<AutoMan>(policy); Stopwatch stopwatch = Stopwatch.StartNew(); for (int i = 0; i < 10; i++) {var m = pool.Get();Console.WriteLine(i + "创建成功" + "id:" + m.Id + "用时:" + stopwatch.ElapsedMilliseconds);stopwatch.Restart();if (i >= 2)//前三个不归还到对象池 {pool.Return(m);} }
运行结果如图:
调用组件从对象池中借走一个对象实例,使用完后应立即归还给对象池,以便重复使用,避免因构造新对象消耗过多资源。
另外,还可以在DefaultObjectPool构造函数中指定对象池的容量。
总结
对象池的使用原则是:有借有还,再借不难。当调用组件从对象池中借走一个对象实例,使用完后应立即归还给对象池,以便重复利用,避免因过多的对象初始化影响系统性能。
对象池主要用在对象初始化比较耗时和使用比较频繁的场景,比如初始化时要读取网络资源,有时候这些对象因为有时效性,又不能用单例(后面会通过自定义策略来说明)。所以说,IOC并不能完全替代对象池。
以上知识来自于:.NET Core 对象池的使用 - 精致码农 - 博客园 (cnblogs.com)
对象池的预热和对象池大小的控制
像缓存一样,为了提高性能,我们可以在系统初始化的时候进行预热。
static void MaxPoolCount() {var poolObjCount = 10;var policy = new DefaultPooledObjectPolicy<AutoMan>();var pool = new DefaultObjectPool<AutoMan>(policy, poolObjCount);//对象池容量为10 List<AutoMan> list = new List<AutoMan>();Console.WriteLine("开始-----------------------------------");for (int i = 0; i < poolObjCount; i++){var m = pool.Get();Console.WriteLine("增加一个对象池对象");list.Add(m);}foreach (AutoMan m in list){pool.Return(m);}Console.WriteLine("预热结束-----------------------------------");List<Task> tasks = new List<Task>();var getCount = 20;for (int i = 0; i < getCount; i++){tasks.Add(Task.Run(() =>{Stopwatch stopwatch = Stopwatch.StartNew();var m = pool.Get();stopwatch.Stop();Console.WriteLine("线程id:" + Thread.CurrentThread.ManagedThreadId + "创建成功,用时:" + stopwatch.ElapsedMilliseconds);}));}Task.WaitAll(tasks.ToArray()); }
运行结果:
通过上述运行结果可以得知:并发请求数超过了对象池数量会导致重新创建对象。
自定义池策略
通过自定义池策略,可以加深对象池的理解,并可以清晰认识到他跟IOC使用场景的区别。
/// <summary> /// 自定义对象池策略 /// </summary> /// <typeparam name="T"></typeparam> public class CustomPooledObjectPolicy : DefaultPooledObjectPolicy<AutoMan> {public override AutoMan Create(){return base.Create();}public override bool Return(AutoMan obj){if (obj == null) { return false; }//对象30秒过期if (obj.CreateTime.AddSeconds(30) < DateTime.Now){return false;}return base.Return(obj);} }
static void CustomPolicy() {var poolObjCount = 10;var policy = new CustomPooledObjectPolicy();var pool = new DefaultObjectPool<AutoMan>(policy, poolObjCount);List<AutoMan> list = new List<AutoMan>();for (int i = 0; i < poolObjCount; i++){var m = pool.Get();Console.WriteLine("增加一个对象池对象");list.Add(m);}//等待40秒,使得归还失败Thread.Sleep(40000);foreach (AutoMan m in list){pool.Return(m);}Console.WriteLine("预热结束-----------------------------------");List<Task> tasks = new List<Task>();for (int i = 0; i < 5; i++){tasks.Add(Task.Run(() =>{Stopwatch stopwatch = Stopwatch.StartNew();var m = pool.Get();stopwatch.Stop();Console.WriteLine("线程id:" + Thread.CurrentThread.ManagedThreadId + "创建成功,用时:" + stopwatch.ElapsedMilliseconds);}));}Task.WaitAll(tasks.ToArray());}
个人理解:如果不需要自定义策略,那使用IOC的单例跟这个对象池是没有区别的。但是需要自定义对象的创建和回收策略,那需要使用对象池