單例(singleton)是非常常見,也非常有用的設計模式,當然了, 面試中也是經常會被問到的:)在幾乎所有的項目中都能看到它的身影。簡而言之,單例保證了一個自定義類型在整個程序的生命週期只被創建一次。要實現一個簡單的單例是也很容易的:
public class Example
{
private static Example instance;
private Example()
{
}
public static Example Instance
{
get
{
if (instance != null)
{
instance = new Example();
}
return instance;
}
}
}
這是大多數人的寫法。
這是一個延遲加載的單例。換句話說,只有當調用Example.Instance時,纔會創建Example的實例。當不存在多線程調用的情況下,這個單例是工作得很好的。但是在現實中,我們已經很少不去考慮線程安全了。提到線程安全,我們第一反應就是加鎖:
public class Example
{
private static Example instance;
private object myLock = new object();
private Example()
{
}
public static Example Instance
{
get
{
lock(myLock)
{
if (instance != null)
{
instance = new Example();
}
return instance;
}
}
}
}
這顯然是沒有問題的,我相信也是大多數程序員的做法。
在面試中如果面試者能夠在沒有任何提示下給出這個線程安全且延遲加載的單例,已經很讓面試官滿意了。 可是我想再吹毛求疵下:能不能實現一個無鎖的線程安全單例?
無鎖且要線程安全。。。我們需要藉助語言的特性。(實際上代碼更少了)
public class Example
{
private static Example instance = new Example();
private Example()
{
}
public static Example Instance
{
get
{
return instance;
}
}
}
的確,無鎖且線程安全了,但是。。。延遲加載似乎又缺失了。也許有人要問,這個延遲加載有什麼用?
絕大多數情況下,這個延遲加載沒有任何用(面試虐人除外),除非這個類還有其他static public methods。譬如說這個Example類有一個靜態方法:
public static string Output()
{
string str;
// process the str.
return str;
}
並且我們只會用到Example.Output()。在這種情況下如果 Example 的instance被創建了,顯然是浪費內存。據我所知,這是延遲加載唯一有用的地方。
不管這個延遲加載用處有多大,我們單純從研究的角度考慮,是否能既做到無鎖,線程安全,又延遲加載?
C#語言的另一大特性幫助了我們:嵌套類。直接上代碼:
public class Example
{
private static Example instance;
private Example()
{
}
public static Example Instance
{
get
{
return Nested.Instance;
}
}
class Nested
{
static Nested()
{
}
internal static readonly Example Instance = new Example();
}
}