現(xiàn)在大多數(shù)Java軟件工程師面試都會(huì)問(wèn)到這個(gè)問(wèn)題:什么是單例模式(Singleton),能否寫出單例模式的示例代碼?單例模式是Gof中23個(gè)模式中最簡(jiǎn)單最容易入門的模式,學(xué)習(xí)它我們能更理性的感知模式的意義.
[形成]
Singleton Pattern 為什么會(huì)出現(xiàn)?在我們軟件開(kāi)發(fā)和架構(gòu)中,經(jīng)常遇到這樣的情形:我們需要一個(gè)類只能且僅能產(chǎn)生一個(gè)實(shí)例..比如表示一臺(tái)計(jì)算機(jī)的類,表示系統(tǒng)設(shè)定的類或者是表示窗口的類,還有為了節(jié)約資源,只讓產(chǎn)生一個(gè)實(shí)例..
如何構(gòu)造這種情形?Singleton模式給我一個(gè)方案:
[代碼示例]
程序列表
名稱
|
說(shuō)明
|
Singleton
|
只有一個(gè)對(duì)象實(shí)例的類
|
Main
|
測(cè)試用的類
|
[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)生一個(gè)對(duì)象實(shí)例
2.把該類的的singleton屬性設(shè)定為static再以Singleton;類的對(duì)象實(shí)例進(jìn)行初始化,這個(gè)初始化的過(guò)程僅加載Sington類的時(shí)候調(diào)用一次.(Line4)
3.把Singleton 類的構(gòu)造函數(shù)限定為private,目的是為了防止從非Singleton類(其他類)調(diào)用構(gòu)造函數(shù)來(lái)產(chǎn)生實(shí)例,如果通過(guò)new方式來(lái)產(chǎn)生Singleton實(shí)例,會(huì)出現(xiàn)編譯錯(cuò)誤.這樣做是為了保險(xiǎn)己見(jiàn).(Line6)
4.要得到Singleton實(shí)例的唯一方法就是調(diào)用類靜態(tài)方法getInstance().這個(gè)名字可以隨便取,只要方便理解就行.(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是同一個(gè)對(duì)象實(shí)例");
11 }else{
12 System.out.println("obj1和obj2不是同一個(gè)對(duì)象實(shí)例");
13 }
14 System.out.println("End");
15 }
16 }
17
Main Class
1.該類是測(cè)試程序.
2.程序通過(guò)getInstance()方式產(chǎn)生兩個(gè)obj1和obj2實(shí)例.(Line 7,Line 8)
3.通過(guò)ojb1= =ojb2表達(dá)式來(lái)確定兩個(gè)對(duì)象是否相同,判定是否產(chǎn)生了Singleton的第二個(gè)示例.(Line9-12)
執(zhí)行結(jié)果含義:
1. 的確如此,obj1和obj2是Singleton類的同一個(gè)且唯一的對(duì)象實(shí)例.
2.當(dāng)程序執(zhí)行后,第一次調(diào)用getInstance的時(shí)候會(huì)初始化Singleton類,同時(shí)也會(huì)初始化static字段,也同時(shí)產(chǎn)生產(chǎn)生了一個(gè)唯一對(duì)象實(shí)例.
[拓展思考]
如下的另一一個(gè)單例模式的程序有什么隱患?
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)生對(duì)象實(shí)例");
9 }
10 public static Singleton2 getInstance(){
11 if(singleton == null){
12 singleton = new Singleton2();
13 }
14 return singleton;
15 }
16
17 }
18
[解答]
當(dāng)多線程同時(shí)調(diào)用Singleton2.getInstance()方法時(shí),可能會(huì)產(chǎn)生多個(gè)對(duì)象實(shí)例,例如

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)生對(duì)象實(shí)例");
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)生對(duì)象實(shí)例.
已產(chǎn)生對(duì)象實(shí)例.
已產(chǎn)生對(duì)象實(shí)例.
B: obj = Singleton2#2a9348
C: obj = Singleton2#b91134
A: obj = Singleton2#e343l12
(#替換為@)
之所以會(huì)知道這種情況是因?yàn)閕f(singleton = = null){ singleton = new Singleton2(); }判斷不夠嚴(yán)謹(jǐn)?shù)膶?dǎo)致。
利用: singleton == null 判斷為空后去執(zhí)行new Singleton2()之前,可能會(huì)有其他線程來(lái)?yè)屜扰袛啾磉_(dá)式singleton == null,從而又執(zhí)行一遍創(chuàng)建實(shí)例的操作。
解決辦法:
給getInstance()方法添加Synchronized修飾符,即可修改成線程安全嚴(yán)謹(jǐn)?shù)膯卫J健?br />
public class Singleton2 {
private static Singleton2 singleton = null;
private Singleton2() {
System.out.println("已產(chǎn)生對(duì)象實(shí)例");
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();
}
}
}