設計模式是一種思想,適合於任何一門面向對象的語言。共有23種設計模式。
單例設計模式所解決的問題就是:保證類的對象在內存中唯一。
舉例:
A、B類都想要操作配置文件信息Config.java,所以在方法中都使用了Config con=new Config();但是這是兩個不同的對象。對兩者的操作互不影響,不符合條件。
解決思路:
1.不允許其他程序使用new創建該類對象。(別人new不可控)
2.在該類中創建一個本類實例。
3.對外提供一個方法讓其他程序可以獲取該對象。
解決方法:單例模式。
步驟:
1.私有化該類的構造函數
2.通過new在本類中創建一個本類對象。
3.定義一個共有的方法將創建的對象返回。
一、餓漢式
雛形:
1 class Single 2 { 3 private static final Single s=new Single(); 4 private Single(){} 5 public static Single getInstance() 6 { 7 return s; 8 } 9 }
為什么方法是靜態的:不能new對象卻想調用類中方法,方法必然是靜態的,靜態方法只能調用靜態成員,所以對象也是靜態的。
為什么對象的訪問修飾符是private,不能是public 嗎?不能,如果訪問修飾符是Public,則Single.s也可以得到該類對象,這樣就造成了不可控。
舉例:
3 public class Single 4 { 5 private String name; 6 public String getName() { 7 return name; 8 } 9 public void setName(String name) { 10 this.name = name; 11 } 12 private static final Single s=new Single(); 13 private Single(){} 14 public static Single getInstance() 15 { 16 return s; 17 } 18 }
1 public class Main { 2 public static void main(String args[]) 3 { 4 Single s=Single.getInstance(); 5 s.setName("張三"); 6 System.out.println(s.getName()); 7 } 8 }
最后結果為:張三
二、懶漢式
前面的是餓漢式單例模式,下面開始講解懶漢式單例模式。
1 class Single 2 { 3 private static Single s=null; 4 private Single(){} 5 public static Single getInstance() 6 { 7 if(s==null) 8 s=new Single(); 9 return s; 10 } 11 }
我們可以看到懶漢式和餓漢式相比的區別就是懶漢式創建了延遲對象同時餓漢式的實例對象是被修飾為final類型。
懶漢式的好處是顯而易見的,它盡最大可能節省了內存空間。
但是懶漢式又有着弊端,在多線程編程中,使用懶漢式可能會造成類的對象在內存中不唯一,雖然通過修改代碼可以改正這些問題,但是效率卻又降低了。而且如果想要使用該類對象,就必須創建對象,所以雖然貌似使用懶漢式有好處,但是在實際開發中使用的並不多。
總結:
懶漢式在面試的時候經常會被提到,因為知識點比較多,而且還可以和多線程結合起來綜合考量。
餓漢式在實際開發中使用的比較多。
三、懶漢式在多線程中的安全隱患以及解決方案、優化策略。
下面分析懶漢式在多線程中的應用和出現的問題以及解決方法。
懶漢式在多線程中出現的問題:
懶漢式由於多加了一次判斷
if(s==null)
導致了線程安全性隱患。因為CPU很有可能在執行完if語句之后切向其它線程。解決線程安全性問題的關鍵就是加上同步鎖。
1.使用同步函數
我們可以直接使用同步函數:
class Single { private static Single s=null; private Single() { } public static synchronized Single getInstance() { if(s==null) s=new Single(); return s; } }
但是直接使用同步函數的方法效率十分低下,因為每次調用此方法都需要先判斷鎖。
2.使用同步代碼塊
我們也可以使用同步代碼塊:
1 class Single 2 { 3 private static Single s=null; 4 private Single() 5 { 7 } 8 public static Single getInstance() 9 { 10 synchronized(Single.class) 11 { 12 if(s==null) 13 s=new Single(); 14 return s; 15 } 16 } 17 }
但是每次調用getInstance方法仍然會判斷鎖,事實上沒有改變效率問題。
3.最終解決方案
我們可以使用另外一種方式,達到只判斷一次鎖,並且實現同步的目的:
1 class Single 2 { 3 private static Single s=null; 4 private Single() 5 { 7 } 8 public static Single getInstance() 9 { 10 if(s==null)//和上面的相比只是多增加了一次判斷 11 { 12 synchronized(Single.class) 13 { 14 if(s==null) 15 s=new Single(); 16 return s; 17 } 18 } 19 return s; 20 } 21 }
觀察代碼可以發現和上面的代碼相比,只是增加了一次判斷而已,但是,這一次判斷卻解決了效率問題。
我們可以分析一下這個代碼:
4.最終解決方案代碼分析和總結
假設我們現在並沒有創建單例對象,即s==null,那么我們調用getInstance方法的時候,會進入if塊,然后進入同步代碼塊,此時,別的線程如果想要創建Single實例,就必須獲取鎖;等當前線程創建完實例對象,釋放鎖之后,假設正巧有幾個線程已經進入了if塊中,它們會拿到鎖,進入同步代碼塊,但是由於進行了判空操作,所以不會創建Single實例,而是直接返回已經創建好的Single實例。如果有多個其他線程進入了if塊,當它們依次進入同步代碼塊的時候,同理也不會創建新的Single實例。而沒有進入if塊的線程,判空操作之后不滿足條件,進不了if塊,而直接執行了下一條語句return s;其后的線程調用getInstance方法時,只會判斷一次s==null,不滿足條件直接返回Single單例s,這樣就大大提高了了執行效率。
總結:在代碼
1 if(s==null) 2 { 3 synchronized(Single.class) 4 { 5 if(s==null) 6 s=new Single(); 7 return s; 8 } 9 } 10 return s;
中,第一行代碼是第一次判空操作,目的是提高效率;第三行代碼是同步代碼塊的入口,目的是保證線程安全;第五行代碼進行第二次判空操作是為了保證單例對象的唯一性
