1.什麼是單例模式?
單例模式是是一種常用的軟件設計模式,最簡單的設計模式之一。
單例模式是一種對象創建型模式,使用單例模式可以保證一個類只生成唯一的實例對象。即在整個程序空間中,該類只產生一個實例對象
應用場景:
-
一些資源管理器常常設計成單例模式。
-
在多個線程之間,比如servlet中,共享同一個資源或操作同一個對象。
-
在整個程序空間使用全局變量,共享資源
-
大規模系統中,爲了性能的考慮,需要節省對象的創建時間等等。
因爲單例模式保證一個類只產生一個實例,所以這些情況下,單例模式就派上用場了。
2.有哪幾種形式?
第一種形式:餓漢式
單例類
/**
* 餓漢式 單例模式
* 可以在多線程中保證,線程安全
*/
public class Person1 {
private String name;
public static Person1 person = new Person1();
//將構造方法私有化,這樣在其他類中無法使用
private Person1(){
}
//提供一個全局的靜態方法
public static Person1 getPerson(){
return person;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
測試類
public class Test {
public static void main(String[] args) {
Person1 p1 = Person1.getPerson();//通過靜態方法來創建對象實例
p1.setName("李四");
System.out.println(p1.getName());
Person1 p2 = Person1.getPerson();
System.out.println(p2 == p1);//true 說明p1和p2的地址相同,同一個對象
System.out.println(p2.getName());//李四 p2並沒有賦值,但是name仍然是李四
p2.setName("張三");
System.out.println(p1.getName());
}
}
餓漢式每次調用的時候不用做創建,直接返回已經創建好的實例。這樣雖然節省了時間,但是卻佔用了空間,實例本身爲static的,會一直在內存中帶着。因爲餓漢單例類在類的初始化時,已經自行實例化,所以線程安全。
第二種形式:懶漢式,也是常用的形式。
/**
* 懶漢式
* 只能在單線程中保證
*/
public class Person2 {
private String name;
private static Person2 person;
//將構造方法私有化,這樣在其他類中無法使用
private Person2(){
}
//提供一個全局的靜態方法
public static Person2 getPerson(){
if(person == null){
person = new Person2();
}
return person;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
顧名思義,lazy loading(延遲加載,一說懶加載),在需要的時候才創建單例對象,而不是隨着軟件系統的運行或者當類被加載器加載的時候就創建。當單例類的創建或者單例對象的存在會消耗比較多的資源,常常採用lazy loading策略。這樣做的一個明顯好處是提高了軟件系統的效率,節約內存資源。
當多個線程併發時,可能會有幾個線程同時進入getPerson()的if語句中,然後分別創建不同的person對象,這就不能保證只有唯一一個對象,所以懶漢式線程不安全,只有單線程時是安全的。
通過使用同步方法(synchronized)修改getPerson()可以使程序符合單例模式要求:
public static synchronized Person2 getPerson(){
if(person == null){
person = new Person2();
}
return person;
}
當某個線程執行同步方法時(synchronized),其他線程不能執行該方法,即獨佔,從而保證只有一個實例。
但是這樣的話,會帶來新的問題。當某個線程在執行該方法時,其他線程就會等待,效率較低。改進的方法是:只需要把需要同步的語句同步,這樣就引出了第三種形式的單例模式。
第三種形式:雙重檢查
/**
* 雙重檢查
* 效率高
*/
public class Person4 {
private String name;
private static Person4 person;
//將構造方法私有化,這樣在其他類中無法使用
private Person4(){
}
//改進:只需要把需要同步的語句同步
//但是可能有多個線程進入第一個if,所以需要第二次判斷
public static Person4 getPerson(){
if(person == null){
synchronized (Person4.class) {
if(person == null){
person = new Person4();
}
}
}
return person;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
這種經過改良的單例模式是線程安全的。
效率提高的原因?將同步內容移動到if內部,提高了執行的效率,不必每次獲取對象時都進行同步,只有第一次才同步,創建了以後就沒必要了。如果雙重if判斷,100的線程可以同時if判斷,理論消耗的時間只有一個if判斷的時間。
爲什麼需要第二次判斷?多個線程併發時,可能有多個線程進入第一個if,所以需要第二次判斷。