單例模式確保一個類只有一個實例,並提供一個全局訪問點。
某些對象我們只需要一個,比如線程池、緩存、注冊表等等。如果這些類擁有多個實例,可能會產生很多問題。
使用單例模式可以確保我們使用的這些全局資源只有一份。
一個經典的單例模式的實現:
- public class Singleton{
- private static Singleton uniqueInstance;
- private Singleton(){}
- public static Singleton getInstance(){
- if(uniqueInstance==null){
- uniqueInstance=new Singleton();
- }
- return uniqueInstance;
- }
- }
由於Singleton類沒有公共的構造方法,我們並不能直接創建這個類的實例,而是只能通過調用靜態的getInstance()方法來獲取對單例對象的一個引用。當調用getInstance()時,如果該類還沒有任何實例則創建一個實例並返回對它的引用,如果已存在一個實例,則直接返回該實例的一個引用。
這樣就確保了單例的類最多只能有一個實例。
多線程下的隱患
在多線程的情況下,如果兩個線程幾乎同時調用getInstance()方法會發生什麼呢?有可能會創建出兩個該類的實例。
我們可以將getInstance()方法變為同步方法來解決這個問題:
- public class Singleton{
- private static Singleton uniqueInstance;
- private Singleton(){}
- public static synchronized Singleton getInstance(){
- if(uniqueInstance==null){
- uniqueInstance=new Singleton();
- }
- return uniqueInstance;
- }
- }
性能問題
然而,事實上,我們只有在uniqueInstance為null的時候才需要進行同步,當這個類已經有實例之後就不存在多線程隱患了。
因此我們將getInstance()方法變為同步方法有可能很大程度的拖垮性能。
如果將getInstance()方法變為同步方法真的影響到了性能,我們可以選擇在靜態初始化時創建這個單例。
- public class Singleton{
- private static Singleton uniqueInstance=new Singleton();
- private Singleton(){}
- public static Singleton getInstance(){
- return uniqueInstance;
- }
- }
這樣自然也能確保單例。
問題是,前面的例子中都是在需要一個實例的時候在創建單例,這個例子中在類初始化時就創建了單例。如果這個對象非常耗資源,而程序中又一直沒有用到它,這樣便是在浪費資源了。
“雙重檢查加鎖”
- public class Singleton{
- private volatile static Singleton uniqueInstance;//volatile修飾被不同線程訪問和修改的變量
- private Singleton(){}
- public static Singleton getInstance(){
- if(uniqueInstance==null){
- synchronized(Singleton.class){//對整個類加鎖
- if(uniqueInstance==null){
- uniqueInstance=new Singleton();
- }
- }
- }
- return uniqueInstance;
- }
- }
這樣就可以在節省資源的同時確保正確性和高效性。只是實現方法有點不夠簡潔了。