池化特征的Singleton – N系统
上述讨论虽然针对最原始单件模式进行了展开,但是实际工程使用中往往可能会提出如下需要:
出于性能的考虑,需要所有的业务逻辑最大可能的基于已有实例。但是为了隔离每个实例,保证每个实例的独立处理,同时又不会因为同时存在过多实例影响节点的总体内存使用,需要允许最多在 N 个实例中共享它们的引用。
其实,很多开发人员在以往的开发中都或多或少的接触过具有上面特征的对象,例如:连接池、线程池等具有池化特性的对象。结合原始单件模式的定义,这里笔者把这类问题需要的结果定义为Singleton – N对象类型。由于 N 的引入,这里其实带来了上面单件“1”模式对象类型所没有讨论到的方面——实例的有效回收。对于单件“1”模式,一般而言不考虑对于这个唯一实例的回收,稍微复杂点的应用也就类似上文增加一个自动更新机制,但是这个“独苗”还是不会被处理掉。不过对于Singleton – N,为了保证其有效的随着吞吐量弹性的使用系统资源,对于企业关键的应用最好为其增加回收机制。
对于按需生成不多于N个实例的部分相对比较容易,需要改造的部分仅包括上面静态Instance属性的get部分。但是对于回收如何选择回收时机,相对而言要复杂很多:回收的过早,会导致客户程序后续调用中没有实例引用可用;回收的太晚,甚至于必须要等到客户实例被GC时同时把引用实例的句柄回收似乎又过晚。尤其是客户程序如果是把这个实例的引用作为某个静态属性使用时,那么一旦实例被分配出去就很难再收回。这里,笔者参考ADO.NET对于连接池的使用,默认将客户端应用假设为不可信任的,只能通过显示Open和Close操作有限的N个实例。同时,考虑到Singleton – N的某些实例将会在客户程序长期引用(甚至是静态引用),因此还需要为Singleton – N外面加个外壳。考虑减少内存占用的问题,外壳本身以单件方式对外提供可能的操作实例。
内部共享资源的管理设计
对于共享资源的使用本身其实是安全的维护每个实体对象的状态,此外还要根据系统的吞吐状态动态的把多余的实例提交给GC。
首先,设计每个工作实体对象:
using System;
using System.Diagnostics;
namespace VisionLogic.DesignPattern.Practice
...{
/**//// <summary>
/// 共享对象的状态
/// </summary>
enum Status
...{
InUse,
NotInUse
}
![]()
/**//// <summary>
/// 共享对象接口
/// </summary>
public interface IWorkItem
...{
void Process();
void Release();
}
![]()
/**//// <summary>
/// 共享对象
/// </summary>
class WorkItem : IWorkItem
...{
private Status status;
private DateTime lastestUsed;
private static readonly DateTime DefaultLatestUsed = new DateTime(2020, 12, 31);
![]()
internal WorkItem()
...{
status = Status.NotInUse;
lastestUsed = DefaultLatestUsed;
}
![]()
/**//// <summary>
/// 工作对象的处理逻辑
/// </summary>
public void Process()
...{
lastestUsed = DateTime.Now;
Trace.WriteLine("do something ...");
}
![]()
/**//// <summary>
/// 显式的引用申请
/// </summary>
public void Allocate()
...{
lastestUsed = DateTime.Now;
status = Status.InUse;
}
![]()
/**//// <summary>
/// 显式的引用释放
/// </summary>
public void Release()
...{
lastestUsed = DateTime.Now;
status = Status.NotInUse;
}
![]()
/**//// <summary>
/// 描述当前共享工作对象的状态
/// </summary>
public Status Status
...{
get ...{ return status; }
set ...{ status = value; }
}
![]()
/**//// <summary>
/// 获得当前共享工作对象最近被访问过的时间
/// </summary>
public DateTime LatestUsed
...{
get ...{ return lastestUsed; }
}
![]()
}
}
![]()
示例5:具有最近访问状态信息的工作实体对象
其中,为了屏蔽外界代码对于工作实体的直接构造,这里把工作实体的构造器访问控制设置为internal,仅仅将引用释放和处理两个方法设计为公共的IWorkItem接口提供给客户端程序使用。为了便于资源的回收,为每个工作对象增加了LastestUsed属性,表示该示例对象最近被访问的时间。
接着,设计内层对于工作实体对象管理的Singleton – N实体:
using System;
using System.Collections.Generic;
namespace VisionLogic.DesignPattern.Practice
...{
/**//// <summary>
/// 内层对于共享工作的管理实体
/// </summary>
static class WorkItemManager
...{
private const int MaxInstances = 5; // 最大的池化数量
private const int RelaseInterval = 1000; // 最长允许的闲置时间
private const int DueTime = 1000; // 时钟的预热时间
private static List<WorkItem> list; // 共享实体集合
private static System.Threading.Timer timer;// 回收控制时钟
![]()
static WorkItemManager()
...{
list = new List<WorkItem>();
timer = new System.Threading.Timer(Release, null, DueTime, RelaseInterval);
}
![]()
/**//// <summary>
/// 申请和动态创建共享实体的过程
/// </summary>
/// <returns></returns>
internal static IWorkItem Allocate()
...{
lock (list)
...{
// 首先使用已有的实例
if(list.Count > 0)
for(int i=0; i<list.Count; i++)
if(list[i].Status == Status.NotInUse)
...{
list[i].Allocate();
return list[i];
}
![]()
// 如果可以创建新的实例
if (list.Count < MaxInstances)
...{
WorkItem workItem = new WorkItem();
workItem.Allocate();
list.Add(workItem);
![]()
return workItem;
}
![]()
// 不能继续申请
return null;
}
}
![]()
/**//// <summary>
/// 获得实际运行的实体数量
/// </summary>
internal static int ActiveInstances
...{
get
...{
lock (list)
...{
if (list.Count == 0)
return 0;
![]()
int count = 0;
for (int i = 0; i < list.Count; i++)
if (list[i].Status == Status.InUse)
count++;
![]()
return count;
}
}
}
![]()
/**//// <summary>
/// 获得池中已经构造出的实体数量
/// </summary>
internal static int Instances
...{
get
...{
lock (list)
...{
return list.Count;
}
}
}
![]()
/**//// <summary>
/// 根据时钟的触发,动态释放引用实例的方法
/// </summary>
/// <param name="state"></param>
private static void Release(object state)
...{
lock (list)
...{
if (list.Count <= 0)
return;
![]()
// 检查是否有需要销毁的实例,并把他们的序号保存在releaseList
List<int> releaseList = new List<int>();
for (int i = 0; i < list.Count; i++)
...{
if (list[i].Status == Status.InUse)
continue;
else
...{
TimeSpan span = DateTime.Now - list[i].LatestUsed;
if (span.TotalMilliseconds > RelaseInterval)
releaseList.Add(i);
}
}
![]()
if (releaseList.Count <= 0)
return;
releaseList.Reverse();
![]()
// 根据releaseList 逐个释放超时的对象
for (int i = 0; i < releaseList.Count; i++)
...{
int index = releaseList[i];
WorkItem workItem = list[index];
list.Remove(workItem);
workItem = null;
}
}
}
}
}
示例6:内层Singleton – N管理对象
其中,Allocate方法向外提供访问实例。为了最大可能的提高资源的使用率,这里对于实例的申请采取了尽可能重用的办法,只有实例集合中再也找不到空闲实例时才会继续生成一个新的实例。由于资源的释放被设计为内部管理对象的内在自动机制,因此Release方法的访问控制为私有的。同时,考虑到运行监控的要求,WorkItemManager还通过静态的ActiveInstances属性和Instances属性向外提供实际的在运行实例数和池中总计的实例数。
