运用共享技术有效地支持大量细粒度的对象。
在.Net中处理字符串时,有一个很重要的机制,叫做字符串驻留机制,就是运用了享元模式。CLR(公共语言运行库)通过维护一个表来存放字符串,该表称为拘留池,它包含程序中以编程方式声明或创建的每个唯一的字符串的一个引用。因此,具有特定值的字符串的实例在系统中只有一个。
CLR中使用享元模式实现字符串驻留的过程:CLR内部维护着一块特殊的数据结构——我们可以把它看成是一个Hash table,这个Hash table维护者大部分创建的string(我这里没有说全部,因为有特例)。这个Hash table的Key对应的相应的string本身,而Value则是分配给这个string的内存块的引用。当CLR初始化的时候创建这个Hash table。一般地,在程序运行过程中,如果需要的创建一个string,CLR会根据这个string的Hash Code试着在Hash table中找这个相同的string,如果找到,则直接把找到的string的地址赋给相应的变量,如果没有则在托管堆中创建一个string,CLR 会先在managed heap中创建该strng,并在Hash table中创建一个Key-Value Pair——Key为这个string本身,Value位这个新创建的string的内存地址,这个地址最重被赋给响应的变量。
通过Strig类和拘留池相关的方法Intern(String str)、IsInterned(String str)的底层实现可以更加直观的了解以上过程。
System.String类
Code
public static String Intern(String str)
{
if (str == null)
{
throw new ArgumentNullException("str");
}
return Thread.GetDomain().GetOrInternString(str);
}
public static String IsInterned(String str)
{
if (str == null)
{
throw new ArgumentNullException("str");
}
return Thread.GetDomain().IsStringInterned(str);
}
System.AppDomain类
Code
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal extern String IsStringInterned(String str);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal extern String GetOrInternString(String str);
sscli20\clr\src\vm\appdomain.cpp
Code
STRINGREF *BaseDomain::IsStringInterned(STRINGREF *pString)
{
CONTRACTL
{
GC_TRIGGERS;
THROWS;
MODE_COOPERATIVE;
PRECONDITION(CheckPointer(pString));
INJECT_FAULT(COMPlusThrowOM(););
}
CONTRACTL_END;
if (m_pStringLiteralMap == NULL)
{
LazyInitStringLiteralMap();
}
_ASSERTE(m_pStringLiteralMap);
return m_pStringLiteralMap->GetInternedString(pString, FALSE, !CanUnload() /* bAppDOmainWontUnload */);
}
STRINGREF *BaseDomain::GetOrInternString(STRINGREF *pString)
{
CONTRACTL
{
GC_TRIGGERS;
THROWS;
MODE_COOPERATIVE;
PRECONDITION(CheckPointer(pString));
INJECT_FAULT(COMPlusThrowOM(););
}
CONTRACTL_END;
if (m_pStringLiteralMap == NULL)
{
LazyInitStringLiteralMap();
}
_ASSERTE(m_pStringLiteralMap);
return m_pStringLiteralMap->GetInternedString(pString, TRUE, !CanUnload() /* bAppDOmainWontUnload */);
}
sscli20\clr\src\vm\stringliteralmap.cpp
Code
STRINGREF *AppDomainStringLiteralMap::GetInternedString(STRINGREF *pString, BOOL bAddIfNotFound, BOOL bAppDomainWontUnload)
{
CONTRACTL
{
GC_TRIGGERS;
THROWS;
MODE_COOPERATIVE;
PRECONDITION(CheckPointer(this));
PRECONDITION(CheckPointer(pString));
}
CONTRACTL_END;
HashDatum Data;
EEStringData StringData = EEStringData((*pString)->GetStringLength(), (*pString)->GetBuffer());
DWORD dwHash = m_StringToEntryHashTable->GetHash(&StringData);
if (m_StringToEntryHashTable->GetValue(&StringData, &Data, dwHash))
{
STRINGREF *pStrObj = NULL;
pStrObj = ((StringLiteralEntry*)Data)->GetStringObject();
_ASSERTE(!bAddIfNotFound || pStrObj);
return pStrObj;
}
else
{
CrstPreempHolder gch(&(SystemDomain::GetGlobalStringLiteralMap()->m_HashTableCrstGlobal));
StringLiteralEntryHolder pEntry(SystemDomain::GetGlobalStringLiteralMap()->GetInternedString(pString, dwHash, bAddIfNotFound));
_ASSERTE(pEntry || !bAddIfNotFound);
// If pEntry is non-null then the entry exists in the Global map. (either we retrieved it or added it just now)
if (pEntry)
{
if (!bAppDomainWontUnload)
{
// Since GlobalStringLiteralMap::GetInternedString() could have caused a GC,
// we need to recreate the string data.
StringData = EEStringData((*pString)->GetStringLength(), (*pString)->GetBuffer());
// Make sure some other thread has not already added it.
if (!m_StringToEntryHashTable->GetValue(&StringData, &Data))
{
// Insert the handle to the string into the hash table.
m_StringToEntryHashTable->InsertValue(&StringData, (LPVOID)pEntry, FALSE);
}
else
{
pEntry.Release(); // while we're under lock
}
}
pEntry.SuppressRelease();
// Retrieve the string objectref from the string literal entry.
STRINGREF *pStrObj = NULL;
pStrObj = pEntry->GetStringObject();
return pStrObj;
}
}
// If the bAddIfNotFound flag is set then we better have a string
// string object at this point.
_ASSERTE(!bAddIfNotFound);
return NULL;
}
sscli20\clr\src\vm目录中包含了 CLI 核心实现,包括垃圾收集器、类装入器、类型系统、错误报告系统、应用程序域、配件、代理支持、反射、安全性和代码管理器。
当以下所有的条件都满足时,可以考虑使用享元模式:
l 一个系统有大量的对象。
l 这些对象耗费大量的内存。
l 这些对象的状态中的大部分都可以外部化。
l 这些对象可以按照内蕴状态分成很多的组,当把外蕴对象从对象中剔除时,每一个组都可以仅用一个对象代替。
l 软件系统不依赖于这些对象的身份,换言之,这些对象可以是不可分辨的。
使用享元模式需要维护一个记录了系统已有的所有享元的表,而这需要耗费资源。因此,应当在有足够多的享元实例可供共享时才值得使用享元模式。