当弱引用对象成为集合元素时
发布:张逸 | 发布时间: 2009年3月4日当我们在系统用到某些占用内存较多的大对象,且该对象并不会被频繁使用(例如缓存场景)时,若考虑性能因素,或许我们可以选择使用弱引用(WeakReference)对象。弱引用对象就像是对象之中的“无间行者”,行走于“活动”与“非活动”状态之间。即使该对象存在引用,垃圾回收器仍然可以对其进行回收,这使得我们对该对象的调用始终存在一种不可预知性,除非我们通过Target属性赋给对象,以创建强引用,否则我们始终处于这种忧虑之中。这让我们常常感到左右为难,但在一些追求性能的场景下,使用弱引用未尝不是明智的选择。只要我们遵循一定的原则,例如在每次调用弱引用对象时,首先判断其是否为null,就不会存在太大的问题(如果考虑并发,则需要lock,通常需要做两次对null的判断,就如在Singleton模式中对并发支持的实现一样)。然而,当我们在一个集合对象中存储弱引用对象时,问题就出现了意想不到的变化。
首先是对集合Count属性的判断。如果一个集合对象在某个时刻存储了10个弱引用对象,当我们调用该集合的Count属性时,返回的值应该是多少?很显然,我们不能做预先的判断。事实上,因为弱引用对象的存在,一个本身线程安全的调用却出现了并发问题。因为在调用Count属性期间,GC正有可能回收集合中的某些元素对象。
其次是对迭代器的支持。我们知道,在对IEnumerable进行Foreach遍历时,不允许我们Add或Remove集合的元素,否则遍历就会失败。如果没有弱引用对象,一切都是美好的。但在我们遍历存储了弱引用对象的集合时,GC就会像幽灵一般,不知什么时候钻出来捣乱,改变集合元素的个数。很显然,这样的集合对迭代器的支持是不安全的。
实质上,这两个问题的本源是相同的,起因就是弱引用的特殊性。如何解决这个问题?一个简单办法是定义自己的弱引用集合,对集合对象进行一个简单的Wrapper,同时隐藏Count属性,以及其对IEnumerable的支持。例如,定义一个弱引用列表:
{
private List<WeakReference> m_list;
public void Add(T object)
{
//这里使用短弱引用,一般不建议使用长弱引用;
m_list.Add(new WeakReference(object));
}
//其它成员
}
WeakReferenceList类并没有提供GetEnumerator()方法的必要,原因如前所述。至于Count属性,我们是否可以通过查询条件,以过滤那些值为null的对象呢?例如:
{
get
{
return m_list.Count(e => e.IsAlive);
}
}
表面看来这是可行的,可是彼时返回的值,在使用该值的时候未必正确,即使对其进行lock,也无法保证其正确性。与其如此,还不如不提供Count属性。
剩下一个问题是如何获得WeakReferenceList的元素?我们需要为其实现特有的索引器。例如:
{
get
{
//这里仍然使用了Count属性(这说明我们可以定义一个私有属性Count)
if (index < 0 || index >= this.Count)
{
throw new IndexOutOfRangeException();
}
else
{
if (m_list[index].Target != null)
{
return (T)m_list[index].Target;
}
else
{
throw new Exception("The object is collected by GC.")
}
}
}
}
private int Count
{
get
{
return m_list.Count(e => e.IsAlive);
}
}
更好地管理弱引用对象的集合是HashTable,这也是缓存的一种好的存储机制。关于HashTable对弱引用对象的应用,请参见Jared Parsons的文章Building a WeakReference Hashtable。实际上,本文正是借鉴了该文的思想。
- 1.uvxwp
- //Singleton pattern implemented
public sealed class CachingService
{
//syncIndicator. -- thread synchonization
private static readonly object syncObject = new object();
//The unique instance. -- volatile to allow multi-threading
private static volatile CachingService serviceInstance;
//Blank default constructor
private CachingService(){}
public static CachingService Value
{
get
{
//in multi-threads, the next statement result will be different,
//because of the execution sequence
if(serviceInstance == null)
{
lock(syncObject)
{
if(serviceInstance == null)
serviceInstance = new CachingService();
}
}
return serviceInstance;
}//end of get
}//end of Value property
} - 2009-3-5 2:24:59 回复该留言
- 2.pzm
- if (i < 0 || i >= this.Count)
{
if (m_list[index].Target != null)
{
return (T)m_list[index].Target;
}
请问i是什么?张逸 于 2009-3-5 9:46:45 回复哎呀,不好意思,将index写成i了。
已经改正。pzm 于 2009-3-5 10:36:24 回复if (index < 0 || index >= this.Count)
{
这样的话这个逻辑是不是有问题啊?
if (index > 0 && index <= this.Count)
这样判断才正确吧张逸 于 2009-3-5 19:02:08 回复还是你细心,昨天晚上写的,看来头有点昏,频犯低级错误。我已经修正过来了。不过如你写的index > 0也不正确。所以我还是使用原来的表达式,只是调整了else的顺序。pzm 于 2009-3-6 10:03:14 回复呵呵 不好意思,正如你说的那样,我写的那个逻辑也不正确.
if (index >= 0 && index < this.Count) 这样才正确.
当然你现在的这种写法也是对的. - 2009-3-5 9:34:51 回复该留言
- 3.tansm
- 微软的企业库里面不是有个很好的弱引用字典的实现吗?作者涉猎不光啊。张逸 于 2009-3-5 19:04:33 回复多谢赐教,我确实涉猎不广。
我从来都不认为自己是知识广博的人。 - 2009-3-5 18:02:12 回复该留言
发表评论
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。






张逸(Bruce Zhang)
