在文章开始之前首先要思考的问题是为什么要建立对象池。这和.NET垃圾回收机制有关,正如下面引用所说,内存不是无限的,垃圾回收器最终要回收对象,释放内存。尽管.NET为垃圾回收已经进行了大量优化,例如将托管堆划分为 3 Generations(代)并设定新建的对象回收的最快,新建的短生命周期对象将进入 Gen 0(新建对象大于或等于 85,000 字节将被看作大对象,直接进入 Gen 2),而 Gen 0 通常情况下分配比较小的内存,因此Gen 0 将回收的非常快。而高频率进行垃圾回收导致 CPU 使用率过高,当 Gen 2 包含大量对象时,回收垃圾也将产生性能问题。

.NET 的垃圾回收器管理应用程序的内存分配和释放。 每当有对象新建时,公共语言运行时都会从托管堆为对象分配内存。 只要托管堆中有地址空间,运行时就会继续为新对象分配空间。 不过,内存并不是无限的。 垃圾回收器最终必须执行垃圾回收来释放一些内存。 垃圾回收器的优化引擎会根据所执行的分配来确定执行回收的最佳时机。 执行回收时,垃圾回收器会在托管堆中检查应用程序不再使用的对象,然后执行必要的操作来回收其内存。参考

构造对象池

.Net Core 在(Base Class Library)基础类型中添加了 ArrayPool,但 ArrayPool 只适用于数组。针对自定义对象,参考MSDN有一个实现,但没有初始化池大小,且从池里取对象的方式比较粗糙,完整的对象池应该包含:

  • 池大小
  • 初始化委托
  • 实例存取方式(FIFO、LIFO 等自定义方式,根据个人需求实现获取实例方式)
  • 获取实例策略

1. 定义对象存取接口,以实现多种存取策略,例如 FIFO、LIFO

/// <summary>
/// 对象存取方式
/// </summary>
public interface IAccessMode<T>
{
/// <summary>
/// 租用对象
/// </summary>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
T Rent(); /// <summary>
/// 返回实例
/// </summary>
/// <param name="item"></param>
void Return(T item);
}

2. 实现存取策略

FIFO

FIFO通过Queue实现,参考

public sealed class FIFOAccessMode<T> : Queue<T>, IAccessMode<T>
{
private readonly int _capacity;
private readonly Func<T> _func;
private int _count; public FIFOAccessMode(int capacity, Func<T> func) : base(capacity)
{
_capacity = capacity;
_func = func;
InitialQueue();
} public T Rent()
{
Interlocked.Increment(ref _count);
return _capacity < _count ? _func.Invoke() : Dequeue();
} public void Return(T item)
{
if (_count > _capacity)
{
var disposable = (IDisposable)item;
disposable.Dispose();
}
else
{
Enqueue(item);
}
Interlocked.Decrement(ref _count);
} private void InitialQueue()
{
for (var i = 0; i < _capacity; i++)
{
Enqueue(_func.Invoke());
}
}
}
LIFO

在LIFO中借助Stack特性实现进栈出栈,因此该策略继承自Stack,参考

public sealed class LIFOAccessModel<T> : Stack<T>, IAccessMode<T>
{
private readonly int _capacity;
private readonly Func<T> _func;
private int _count; public LIFOAccessModel(int capacity, Func<T> func) : base(capacity)
{
_capacity = capacity;
_func = func;
InitialStack();
} public T Rent()
{
Interlocked.Increment(ref _count);
return _capacity < _count ? _func.Invoke() : Pop();
} public void Return(T item)
{
if (_count > _capacity)
{
var disposable = (IDisposable)item;
disposable.Dispose();
}
else
{
Push(item);
}
Interlocked.Decrement(ref _count);
} private void InitialStack()
{
for (var i = 0; i < _capacity; i++)
{
Push(_func.Invoke());
}
}
}

注意:以上两个实现都遵循池容量不变原则,但租用的实例可以超过对象池大小,返还时还将检测该实例直接释放还是进入池中。而如何控制池大小和并发将在下面说明。

3.Pool实现

public class Pool<T> : IDisposable where T : IDisposable
{
private int _capacity;
private IAccessMode<T> _accessMode;
private readonly object _locker = new object();
private readonly Semaphore _semaphore; public Pool(AccessModel accessModel, int capacity, Func<T> func)
{
_capacity = capacity;
_semaphore = new Semaphore(capacity, capacity);
InitialAccessMode(accessModel, capacity, func);
} private void InitialAccessMode(AccessModel accessModel, int capacity, Func<T> func)
{
switch (accessModel)
{
case AccessModel.FIFO:
_accessMode = new FIFOAccessMode<T>(capacity, func);
break;
case AccessModel.LIFO:
_accessMode = new LIFOAccessModel<T>(capacity, func);
break;
default:
throw new NotImplementedException();
}
} public T Rent()
{
_semaphore.WaitOne();
return _accessMode.Rent();
} public void Return(T item)
{
_accessMode.Return(item);
_semaphore.Release();
} public void Dispose()
{
if (!typeof(IDisposable).IsAssignableFrom(typeof(T))) return; lock (_locker)
{
while (_capacity > 0)
{
var disposable = (IDisposable)_accessMode.Rent();
_capacity--;
disposable.Dispose();
} _semaphore.Dispose();
}
}
}

在Pool中如何控制程序池并发,这里我们引入了 Semaphore 以控制并发,这里将严格控制程序池大小,避免内存溢出。

4.使用

Student 类用作测试

public class Student : IDisposable
{
public string Name { get; set; } public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
} private bool _disposed; protected virtual void Dispose(bool disposing)
{
if (_disposed)
return; if (disposing)
{
Name = null;
//Free any other managed objects here.
} _disposed = true;
}
}
public void TestPool()
{
Func<Student> func = NewStudent;
var pool = new Pool<Student>(AccessModel.FIFO, 2, func);
for (var i = 0; i < 3; i++)
{
Student temp = pool.Rent();
//todo:Some operations
pool.Return(temp);
} Student temp1 = pool.Rent(); pool.Return(temp1); pool.Dispose();
} public Student NewStudent()
{
return new Student();
}

总结:至此,一个完整的对象池建立完毕。

安装与使用:现已发布到NuGet服务器,可在程序包管理控制台中输入安装命令使用。

Install-Package CustomObjectPool

Object Pooling(对象池)实现的更多相关文章

  1. Object Pool 对象池的C++11使用(转)

    很多系统对资源的访问快捷性及可预测性有严格要求,列入包括网络连接.对象实例.线程和内存.而且还要求解决方案可扩展,能应付存在大量资源的情形. object pool针对特定类型的对象循环利用,这些对象 ...

  2. Java堆外内存之一:堆外内存场景介绍(对象池VS堆外内存)

    最近经常有人问我在Java中使用堆外(off heap)内存的好处与用途何在.我想其他面临几样选择的人应该也会对这个答案感兴趣吧. 堆外内存其实并无特别之处.线程栈,应用程序代码,NIO缓存用的都是堆 ...

  3. 设计模式之美:Object Pool(对象池)

    索引 意图 结构 参与者 适用性 效果 相关模式 实现 实现方式(一):实现 DatabaseConnectionPool 类. 实现方式(二):使用对象构造方法和预分配方式实现 ObjectPool ...

  4. 对象池 object pool

    对象池适用于: 对象的创建很耗时 对象频繁地释放再创建 对象池的实现:将释放的对象放进对象池中,在新建对象时,从对象池取对象 public class ObjectPool<T> wher ...

  5. [译]Unity3D内存管理——对象池(Object Pool)

    原文地址:C# Memory Management for Unity Developers (part 3 of 3), 其实从原文标题可以看出,这是一系列文章中的第三篇,前两篇讲解了从C#语言本身 ...

  6. Java小对象的解决之道——对象池(Object Pool)的设计与应用

    一.概述 面向对象编程是软件开发中的一项利器,现已经成为大多数编程人员的编程思路.很多高级计算机语言也对这种编程模式提供了很好的支持,例如C++.Object Pascal.Java等.曾经有大量的软 ...

  7. 对象池模式(Object Pool Pattern)

    本文节选自<设计模式就该这样学> 1 对象池模式的定义 对象池模式(Object Pool Pattern),是创建型设计模式的一种,将对象预先创建并初始化后放入对象池中,对象提供者就能利 ...

  8. 对象池化技术 org.apache.commons.pool

    恰当地使用对象池化技术,可以有效地减少对象生成和初始化时的消耗,提高系统的运行效率.Jakarta Commons Pool组件提供了一整套用于实现对象池化的框架,以及若干种各具特色的对象池实现,可以 ...

  9. 通用对象池ObjectPool的一种简易设计和实现方案

    对象池,最简单直接的作用当然是通过池来减少创建和销毁对象次数,实现对象的缓存和复用.我们熟知的线程池.数据库连接池.TCP连接池等等都是非常典型的对象池. 一个基本的简易对象池的主要功能实现我认为应该 ...

随机推荐

  1. 我的 FPGA 学习历程(10)—— 实验:数码管驱动

    根据黑金 AX301 手册,数码管位选信号命名为 SEL[5:0],其中 SEL[5] 对应最左边的数码管,而SEL[0] 对应最右边数码管:作为约定,在下面的描述中我们对应的称之为数码管 5 和数码 ...

  2. [转]C# 使用Conditional特性而不是#if条件编译

    转自: http://www.cnblogs.com/xibei666/p/5495561.html 概述 #if/#endif 语句常用来基于同一份源码生成不同的编译结果,其中最常见的就是debug ...

  3. 通过命令行操作MYSQL的方法 以及导入大的SQL备份文件

    运行  输入CMD 进入 命令行窗口 输入Mysql.exe的路径  如:c:/wamp/bin/mysql.exe  回车 这时出现 welcome to the mysql ...的提示  进入成 ...

  4. 2、java基础

    1.注释  -----> 注释不会出现在字节码文件中.即Java编译器编译时会跳过注释语句. // 单行注释 ,注释内容从//到本行末尾 /*  */ 多行注释,/* */ 注释不能嵌套 /** ...

  5. lvm快照备份数据库(Mysql5.7)

    备份的目的 能够防止由于机械故障以及人为误操作带来的数据丢失,例如将数据库文件保存在了其它地方. 备份的分类 以操作过程中服务的可用性分: 冷备份:cold backup mysql服务关闭,mysq ...

  6. 马昕璐201771010118 《面对对象程序设计(java)》第九周学习总结

    第一部分:理论知识学习部分 异常:在程序的执行过程中所发生的异常事件,它中断指令的正常执行. Java把程序运行时可能遇到的错误分为两类: 非致命异常:通过某种修正后程序还能继续执行. 致命异常:程序 ...

  7. ES6新增的常用数组方法(forEach,map,filter,every,some)

    ES6新增的常用数组方法 let arr = [1, 2, 3, 2, 1]; 一 forEach => 遍历数组 arr.forEach((v, i) => { console.log( ...

  8. Linux 管理进程

    探查进程 参数 描述 -A 显示所有进程 -N 显示与指定参数不符的所有进程 -a 显示除控制进程(session leader1)和无终端进程外的所有进程 -d 显示除控制进程外的所有进程 -e 显 ...

  9. 【高并发架构】Redis特点及构件模型

    数据结构 redis 相比 memcached 来说,拥有更多的数据结构,能支持更丰富的数据操作.如果需要缓存能够支持更复杂的结构和操作, redis 会是不错的选择. redis 主要有以下几种数据 ...

  10. ubuntu解压时中文出现乱码

    一.乱码类似这样的:╫╩┴╧╖┤╤▌▓т╒╛╦┘╢╚│ 今天遇到需要上传十几G的图片,在wins上压缩成zip格式,在上传到服务器上,结果出现乱码.然后各种百度心塞. 最初查到原因: 这个主要是因为z ...