當弱引用物件成為集合元素時

技術小美發表於2017-11-09

當我們在系統用到某些佔用記憶體較多的大物件,且該物件並不會被頻繁使用(例如快取場景)時,若考慮效能因素,或許我們可以選擇使用弱引用(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。實際上,本文正是借鑑了該文的思想。

本文轉自wayfarer51CTO部落格,原文連結:http://blog.51cto.com/wayfarer/280097,如需轉載請自行聯絡原作者


相關文章