转载

设计模式-借还模式

我个人比较注重代码的性能,包括时间和空间,代码的可控度;在做开发的时候偶然发现并实现了这个方式,我个人命名他为“借还模式”。不知道是否有相关的模式,我读书少,嘿嘿。。。

很多时候,我们执行一个方法,必须要有很多的new操作,当这个方法执行次数非常多的时候,这个方法内部new的操作将是超级多的。但是我们又不能手动释放内存,只能等待GC来回收。那么我们就有一个问题,既然不能手动释放,而且反正要new,那么干脆我们就不让GC去回收了,让他持续的存在,这样还能节省内存的空间,不是一举两得吗?由此我想到图书馆的图书实现过程,图书的所有权是图书馆的,借出去的是使用权,用完了必须归还。那么就有两个关键的操作“借”和“还”,“借”和“还”都指的是使用权。

代码实现如下:

  1     /// <summary> 对象借还管理器 </summary>   2     public class BRManage : IDisposable   3     {   4         private static BRManage br;   5         private static readonly object ins = new object();   6         private readonly object globalLock = new object();   7            8         private const int WARNING_COUNT = 10;   9         private readonly Dictionary<Type, Queue<object>> cachePool = new Dictionary<Type, Queue<object>>();  10         private readonly Dictionary<Type, List<object>> cacheBak = new Dictionary<Type, List<object>>();  11           12         private bool isdisposing;  13   14         private BRManage()  15         {  16         }  17   18         /// <summary> 单例 </summary>  19         public static BRManage Ins  20         {  21             get  22             {  23                 if (br == null)  24                 {  25                     lock (ins)  26                     {  27                         if (br == null)  28                         {  29                             br = new BRManage();  30                         }  31                     }  32                 }  33   34                 return br;  35             }  36         }  37   38         ///<summary> 借出一个对象 </summary>  39         public T Borrow<T>() where T : class, new()  40         {  41             if (isdisposing)  42                 throw new ApplicationException("借还管理器已经释放,不能再使用,请重启系统");  43   44             var type = typeof (T);  45   46             if (!cacheBak.ContainsKey(type))  47             {  48                 lock (globalLock)  49                 {  50                     if (!cacheBak.ContainsKey(type))  51                     {  52                         cachePool.Add(type, new Queue<object>());  53                         cacheBak.Add(type, new List<object>());  54                     }  55                 }  56             }  57   58             var que = cachePool[type];  59             var list = cacheBak[type];  60   61             var bak = WARNING_COUNT - que.Count;  62             if (bak > 0)  63             {  64                 for (int i = 0; i < bak; i++)  65                 {  66                     var temp = new T ();  67                     list.Add(temp);  68                     que.Enqueue(temp);  69                 }  70             }  71   72             return que.Dequeue() as T;  73         }  74   75         ///<summary> 返还一个对象 </summary>  76         public void Return<T>(T obj) where T : class  77         {  78             if (isdisposing)  79                 throw new ApplicationException("借还管理器已经释放,不能再使用,请重启系统");  80   81             if (obj == null)  82                 throw new ArgumentNullException("obj");  83   84             var type = obj.GetType();  85             if (!cachePool.ContainsKey(type))  86             {  87                 throw new ArgumentNullException("obj", "该类型未注册过");  88             }  89   90             if (cacheBak[type].IndexOf(obj) == -1)  91             {  92                 throw new ArgumentException("该对象未被索引,不被借还管理", "obj");  93             }  94               95             cachePool[type].Enqueue(obj);  96         }  97   98         /// <summary> 释放所有数据 </summary>  99         public void Dispose() 100         { 101             isdisposing = true; 102  103             var list = new List<Type>(cachePool.Keys); 104             for (int i = 0, l = list.Count; i < l; i++) 105             { 106                 cachePool[list[i]].Clear(); 107                 cachePool[list[i]].TrimExcess(); 108             } 109             cachePool.Clear(); 110             list.Clear(); 111  112             list = new List<Type>(cacheBak.Keys); 113             for (int i = 0, l = list.Count; i < l; i++) 114             { 115                 cacheBak[list[i]].Clear(); 116                 cacheBak[list[i]].TrimExcess(); 117             } 118             cacheBak.Clear(); 119             list.Clear(); 120             list.TrimExcess(); 121         } 122     }

代码解释:

这里单例是为了方便操作,我不喜欢new,也不喜欢static。new代表着空间减少,static如果单纯的作为计算数据没问题,如果保存数据带来并发问题,避免这些问题要增加复杂度。cacheBak的作用是保存对象的所有权,作用是在Dispose的时候释放列表。当然Dispose这里比较简单,在实际用的时候可能需要调用对象的Close或者Dispose方法。cachePool是一个队列,队列是个好东西,这里用来记录对象的使用权,表示还有那些对象可供借出。这里借出对象是没有选择性的,实际根据业务可以有选择的借出。另外有一个WARNING_COUNT常量,相当于警告库存的概念,让使用权队列始终大于10个长度,这样避免并发操作Dequeue抛出操作异常的问题,当然是一定程度上。

借还模式注意事项:

  1. Borrow和Return必须成对出现,正所谓“有借有还再借不难”,否则就是人品问题;那么代码就是质量问题,如果不Return的后果可能是内存占用量急剧增长。
  2. 这里用了泛型方法和泛型约束,要求T必须是class,很显然如果是值类型,按副本传递就失去了意义;并且可以new,因为在Borrow里面需要创建对象。
  3. 在使用完毕的时候,一般系统或者程序关闭的时候执行Dispose,释放使用权和所有权。
  4. 如果需要的话,可以在Return或者Dispose的时候可以对对象进行一些操作。
  5. 对于非托管代码慎用,例如对同一个文件操作的Stream可能会出现不可以意料的异常。

借换模式的扩展:

  1. 可以增加限制让cacheBak所有权列表的长度,不要让所有权列表一直增长下去,可以限制在某个界限值,如果没有可用使用权对象,线程就Sleep一会。
  2. 可以对Type类型做限制,增加安全性,可以增加一个Register方法,对能使用的Type进行注册,而不是在Borrow中自动创建
  3. 如果Return的对象在其他地方还有保留的引用,而我又不想让这些保留的引用使用的时候,可以在Return的时候对这个对象进行一些的操作,防止其他保留的引用继续使用,当然比较麻烦。

使用场景:

  1. 大量小对象的创建,可以节省内存
  2. 珍惜资源的创建,嘿嘿,你是否想到了不关闭的Connection对象,我这么用了,目前没问题。

测试结果:

先看测试代码如下

class Program {  static void Main(string[] args)  {   var dt = DateTime.Now;   // A 方式   //for (int i = 0; i < 10000000; i++)   //{   // var o = new MyClass();   // o.p1 = 123;   // o.p2 = o.p1;   // o.p3 = 12;   // o.p4 = o.p1.ToString();   // o.p5 = Guid.NewGuid();   //}   //Console.WriteLine((DateTime.Now-dt).TotalMilliseconds);   // B 方式   dt = DateTime.Now;   for (int i = 0; i < 10000000; i++)   {    var o = BRManage.Ins.Borrow<MyClass>();    BRManage.Ins.Return(o);   }   Console.WriteLine((DateTime.Now - dt).TotalMilliseconds);   Console.ReadLine();  } } class MyClass {  public int p1 { get; set; }  public int p2 { get; set; }  public byte p3 { get; set; }  public string p4 { get; set; }  public Guid p5 { get; set; } } 
CPU极值 执行时间(ms) 最高内存占用(m)
A方案 35% 4300-4500 3.7M
B方案 35% 4300-4500 2.1M

测试比较片面,实际应用的时候比较复杂,整个系统测试比较麻烦,个人认为总的占用空间会减少,同时性能不会降低。

综述:该模式依然是创建模式,主要功能是能节省一定的空间,例子代码中的性能和直接new的性能是一样的,测试的时候有上下浮动,如果对Borrow和Return进行其他操作的时候多多少少会影响一些性能,大家就自己斟酌吧。欢迎园友拍砖,丢鸡蛋。

正文到此结束
Loading...