現(xiàn)在大多數(shù)Java軟件工程師面試都會問到這個問題:什么是單例模式(Singleton),能否寫出單例模式的示例代碼?單例模式是Gof中23個模式中最簡單最容易入門的模式,學習它我們能更理性的感知模式的意義.
[形成]
Singleton Pattern 為什么會出現(xiàn)?在我們軟件開發(fā)和架構(gòu)中,經(jīng)常遇到這樣的情形:我們需要一個類只能且僅能產(chǎn)生一個實例..比如表示一臺計算機的類,表示系統(tǒng)設(shè)定的類或者是表示窗口的類,還有為了節(jié)約資源,只讓產(chǎn)生一個實例..
如何構(gòu)造這種情形?Singleton模式給我一個方案:
[代碼示例]
程序列表
名稱
|
說明
|
Singleton
|
只有一個對象實例的類
|
Main
|
測試用的類
|
[UML圖]

[示例代碼和類的詮釋]
1 package singleton;
2
3 public class Singleton {
4 private static Singleton singleton = new Singleton();
5
6 private Singleton() {
7 System.out.println("Create instance...");
8 }
9
10 public static Singleton getInstance() {
11 return singleton;
12 }
13 }
14
Singleton Class:
1.該類只能產(chǎn)生一個對象實例
2.把該類的的singleton屬性設(shè)定為static再以Singleton;類的對象實例進行初始化,這個初始化的過程僅加載Sington類的時候調(diào)用一次.(Line4)
3.把Singleton 類的構(gòu)造函數(shù)限定為private,目的是為了防止從非Singleton類(其他類)調(diào)用構(gòu)造函數(shù)來產(chǎn)生實例,如果通過new方式來產(chǎn)生Singleton實例,會出現(xiàn)編譯錯誤.這樣做是為了保險己見.(Line6)
4.要得到Singleton實例的唯一方法就是調(diào)用類靜態(tài)方法getInstance().這個名字可以隨便取,只要方便理解就行.(Line 10)
1 package singleton;
2
3 public class Main {
4
5 public static void main(String[] args) {
6 System.out.println("Start");
7 Singleton obj1 = Singleton.getInstance();
8 Singleton obj2 = Singleton.getInstance();
9 if(obj1 == obj2){
10 System.out.println("obj1和obj2是同一個對象實例");
11 }else{
12 System.out.println("obj1和obj2不是同一個對象實例");
13 }
14 System.out.println("End");
15 }
16 }
17
Main Class
1.該類是測試程序.
2.程序通過getInstance()方式產(chǎn)生兩個obj1和obj2實例.(Line 7,Line 8)
3.通過ojb1= =ojb2表達式來確定兩個對象是否相同,判定是否產(chǎn)生了Singleton的第二個示例.(Line9-12)
執(zhí)行結(jié)果含義:
1. 的確如此,obj1和obj2是Singleton類的同一個且唯一的對象實例.
2.當程序執(zhí)行后,第一次調(diào)用getInstance的時候會初始化Singleton類,同時也會初始化static字段,也同時產(chǎn)生產(chǎn)生了一個唯一對象實例.
[拓展思考]
如下的另一一個單例模式的程序有什么隱患?
1 package singleton;
2
3 public class Singleton2 {
4
5 private static Singleton2 singleton = null;
6
7 private Singleton2(){
8 System.out.println("已產(chǎn)生對象實例");
9 }
10 public static Singleton2 getInstance(){
11 if(singleton == null){
12 singleton = new Singleton2();
13 }
14 return singleton;
15 }
16
17 }
18
[解答]
當多線程同時調(diào)用Singleton2.getInstance()方法時,可能會產(chǎn)生多個對象實例,例如

public class Main extends Thread
{


public static void main(String[] args)
{
System.out.println("Starts.");
new Main("A").start();
new Main("B").start();
new Main("C").start();
System.out.println("End.");
}

public void run()
{
Singleton2 obj = Singleton2.getInstance();
System.out.println(getName()+": obj ="+obj);
}

public Main(String name)
{
super(name);
}
}


public class Singleton2
{
private static Singleton2 singleton2 = null;

private Singleton2()
{
System.out.println("已產(chǎn)生對象實例");
solwDown();
}


public static Singleton2 getInstance()
{

if(singleton2 == null)
{
singleton2 = new Singleton2();
}
return singleton2;
}

private void solwDown()
{

try
{
Thread.sleep(1000);

}catch(InterruptedException e)
{
e.printStackTrace();
}
}
}

執(zhí)行結(jié)果:
Start.
End.
已產(chǎn)生對象實例.
已產(chǎn)生對象實例.
已產(chǎn)生對象實例.
B: obj = Singleton2#2a9348
C: obj = Singleton2#b91134
A: obj = Singleton2#e343l12
(#替換為@)
之所以會知道這種情況是因為if(singleton = = null){ singleton = new Singleton2(); }判斷不夠嚴謹?shù)膶е隆?br />
利用: singleton == null 判斷為空后去執(zhí)行new Singleton2()之前,可能會有其他線程來搶先判斷表達式singleton == null,從而又執(zhí)行一遍創(chuàng)建實例的操作。
解決辦法:
給getInstance()方法添加Synchronized修飾符,即可修改成線程安全嚴謹?shù)膯卫J健?br />
public class Singleton2 {
private static Singleton2 singleton = null;
private Singleton2() {
System.out.println("已產(chǎn)生對象實例");
solwDown();
}
public static synchronized Singleton2 getInstance() {
if (singleton == null) {
singleton = new Singleton2();
}
return singleton;
}
private void solwDown() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}