当弱引用对象成为集合元素时

时间:2021-08-31 04:46:26

当我们在系统用到某些占用内存较多的大对象,且该对象并不会被频繁使用(例如缓存场景)时,若考虑性能因素,或许我们可以选择使用弱引用(WeakReference)对象。弱引用对象就像是对象之中的“无间行者”,行走于“活动”与“非活动”状态之间。即使该对象存在引用,垃圾回收器仍然可以对其进行回收,这使得我们对该对象的调用始终存在一种不可预知性,除非我们通过Target属性赋给对象,以创建强引用,否则我们始终处于这种忧虑之中。这让我们常常感到左右为难,但在一些追求性能的场景下,使用弱引用未尝不是明智的选择。只要我们遵循一定的原则,例如在每次调用弱引用对象时,首先判断其是否为null,就不会存在太大的问题(如果考虑并发,则需要lock,通常需要做两次对null的判断,就如在Singleton模式中对并发支持的实现一样)。然而,当我们在一个集合对象中存储弱引用对象时,问题就出现了意想不到的变化。

首先是对集合Count属性的判断。如果一个集合对象在某个时刻存储了10个弱引用对象,当我们调用该集合的Count属性时,返回的值应该是多少?很显然,我们不能做预先的判断。事实上,因为弱引用对象的存在,一个本身线程安全的调用却出现了并发问题。因为在调用Count属性期间,GC正有可能回收集合中的某些元素对象。

其次是对迭代器的支持。我们知道,在对IEnumerable进行Foreach遍历时,不允许我们Add或Remove集合的元素,否则遍历就会失败。如果没有弱引用对象,一切都是美好的。但在我们遍历存储了弱引用对象的集合时,GC就会像幽灵一般,不知什么时候钻出来捣乱,改变集合元素的个数。很显然,这样的集合对迭代器的支持是不安全的。

实质上,这两个问题的本源是相同的,起因就是弱引用的特殊性。如何解决这个问题?一个简单办法是定义自己的弱引用集合,对集合对象进行一个简单的Wrapper,同时隐藏Count属性,以及其对IEnumerable的支持。例如,定义一个弱引用列表:

public sealed class WeakReferenceList<T>
{
    private List<WeakReference> m_list;

    public void Add(T object)
    {      
        //这里使用短弱引用,一般不建议使用长弱引用;
        m_list.Add(new WeakReference(object));
    }
    //其它成员
}

WeakReferenceList类并没有提供GetEnumerator()方法的必要,原因如前所述。至于Count属性,我们是否可以通过查询条件,以过滤那些值为null的对象呢?例如:

public int Count
{
    get
    {
        return m_list.Count(e => e.IsAlive);
    }
}

表面看来这是可行的,可是彼时返回的值,在使用该值的时候未必正确,即使对其进行lock,也无法保证其正确性。与其如此,还不如不提供Count属性。

剩下一个问题是如何获得WeakReferenceList的元素?我们需要为其实现特有的索引器。例如:

public T this[int index]
{
    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。实际上,本文正是借鉴了该文的思想。

本文出自 “晴窗笔记” 博客,请务必保留此出处http://wayfarer.blog.51cto.com/1300239/280097