二、單例模式(Singleton Pattern)

來源:互聯網
上載者:User
一、單例模式的定義

單例模式屬於對象建立性模式,具備以下特點的類成為單例類。

1、該類只有一個唯一的執行個體

2、該唯一的執行個體是由自己執行個體化的

3、該類自行向整個系統提供這個執行個體


應該說單例模式是許多設計模式中最簡單的一種,它只涉及到該類本身。但是,它的思想和功能卻是強大的。在實際開發中,單例模式在什麼地方會用到呢?

比如說線程池,我們一般只需要一個線程池去管理線程,這裡就可以使用單例。又比如jdbc中的資料庫連接,因為開啟一個串連要消耗大量的資源,所以這裡也可以使用單例。


二、單例模式的優點和適用情境

優點:

1、在記憶體中只有一個對象,節省記憶體空間。

2、避免頻繁的建立銷毀對象,可以提高效能。

3、避免對共用資源的多重佔用。

4、可以全域訪問。


適用情境:由於單例模式的以上優點,所以是編程中用的比較多的一種設計模式。比較常見的適用情境:

1、需要頻繁執行個體化然後銷毀的對象。

2、建立對象時耗時過多或者耗資源過多,但又經常用到的對象。

3、有狀態的工具類對象。

4、頻繁訪問資料庫或檔案的對象。

5、以及其他我沒用過的所有要求只有一個對象的情境。


三、單例模式的各種實現方式以及注意事項
1、首先看經典的單例實現方式,也稱為為懶漢式單例

public class Singleton {private static Singleton singleton;private Singleton() {}public static Singleton getInstance(){if(singleton==null)singleton=new Singleton();return singleton;}}

在這種情況下,通過將構造器私人化的方式,來避免在類外對該類進行執行個體化(暫時不討論反射的情況),然後通過一個公有的靜態方法,執行個體化唯一的執行個體,並向整個系統提供該執行個體。

因為只有我們需要使用改類的執行個體的時候,才會去調用靜態方法getInstance(),才會去執行個體化這個唯一的執行個體,而並不是在類載入的時候就去執行個體化這個唯一的執行個體,所以稱為懶漢式單例。


實現非常的簡潔,但是確實有瑕疵的。在多線程的情況下,你可能會產生多個執行個體,而並不是你所期待的那樣:唯一的一個。也就是,上面的方式,實際上並不是安全執行緒的。

假設線上程1中,你需要使用Singleton的執行個體,你調用getInstance()方法,因為此前並沒有在任何地方調用過getInstance()方法,所以if(singleton==null) 判斷為true,那麼就要進入if代碼塊,進行執行個體化。而在這個時候,線上程2中,也需要使用Singleton的執行個體,因為線上程1中只是進入了if代碼塊,還沒來得及執行個體化,所以線上程2中,if(singleton==null)判斷依然為true,也進入了if代碼塊。問題出來了,因為多線程的並發問題,導致了得到了兩個不一樣的Singleton類的執行個體。違背了單例設計模式的初衷了。


解決單例中安全執行緒的方式有很多種,下面仔細介紹。


2、懶漢式單例模式的改寫——直接加上線程同步

public class Singleton {private static Singleton singleton;private Singleton() {}public static synchronized Singleton getInstance(){if(singleton==null){singleton=new Singleton();}return singleton;}}

在getInstance()方法上,直接加上synchronized關鍵字,將它變為同步方法,那麼安全執行緒問題非常輕鬆的解決了。可以這樣做,卻是大大降低了效率。因為線程同步時非常消耗資源的。所以不推薦使用這種方法

3、懶漢式單例模式的改寫——雙重檢查加鎖
public class Singleton {private static volatile Singleton singleton;private Singleton() {}public static Singleton getInstance(){if(singleton==null){synchronized (Singleton.class) {if(singleton==null)singleton=new Singleton();}}return singleton;}}

如果如第2種方式,直接同步處理方法,那麼每一次調用getInstance()方法的時候,都會進行同步,效率大大的降低了。使用雙重檢查加鎖,只有當singleton為null的時候,才會進入到同步代碼塊。

要注意:singleton的聲明一定要加上volatile關鍵字,看到許多人使用雙重檢查加鎖的時候 都少了這個volatile關鍵字,這樣是不正確的。關於這個volatile的用處,看以參考下面的文章:http://blog.csdn.net/lonelyroamer/article/details/8133572中的 
清單 3. 將 volatile 變數用於一次性安全發布

注意:在jdk1.4及更早的版本中,許多JVM對於volatile關鍵字的實現會導致雙重檢查加鎖的失效。所以只能在jdk1.5之後使用。

4、懶漢式單例模式的改寫——餓漢式單例

public class Singleton {private static final Singleton singleton=new Singleton();private Singleton() {}public static  Singleton getInstance(){return singleton;}}

直接在類載入的時候就將singleton執行個體化,這樣就能保持只有一個唯一的執行個體,也就沒有上面所說的安全執行緒的問題。

4、使用靜態私人內部類建立單例執行個體

public class Singleton {//靜態內部私人類private static class SingletonHolder {static final Singleton uniqueInstance = new Singleton();}private Singleton() {}public static Singleton getInstance() {return SingletonHolder.uniqueInstance;}}

因為靜態私人內部類SingletonHolder不會隨Singleton的載入而載入,它只會在使用到它的時候再去載入。所以,它具有懶漢式延遲執行個體化的特點,並且在類載入初始化的時候,所有變數都是自動同步的,也就是說是安全執行緒的。
這種方式,十分的巧妙,有懶漢式單例的延遲建立,又沒有它的安全執行緒問題。個人覺得這個方式在java中非常不錯。

5、單例的繼承結構---註冊式單例

看代碼:

package com.tao.pattern;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;import java.util.HashMap;public class JavaTest{public static void main(String[] args) {SingletonChild.getInstance().show();}} class Singleton {protected Singleton(){}private static final HashMap<String,Singleton> m_register=new HashMap<String, Singleton>();public static Singleton getInstance(Class<? extends Singleton> clazz){if(clazz!=null){if(!m_register.containsKey(clazz.getName())){synchronized(Singleton.class){if(!m_register.containsKey(clazz.getName())){try {Constructor<? extends Singleton> constructor=clazz.getDeclaredConstructor(new Class[]{});constructor.setAccessible(true);Singleton singleton=constructor.newInstance();if(singleton!=null)m_register.put(clazz.getName(), singleton);} catch (NoSuchMethodException | SecurityException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (IllegalArgumentException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}}}return m_register.get(clazz.getName());}else {return null;}}}class SingletonChild extends Singleton{private SingletonChild(){}public static SingletonChild getInstance(){return (SingletonChild)Singleton.getInstance(SingletonChild.class);}public void show(){System.out.println("我已經註冊了");}}

這種方式用的很少,主要用於單例模式的繼承結構,讓子類都能實現單例。一般,當你要使用這種方式的時候,還是要看看自己的設計是否有問題。

四、單例模式的使用注意

1、記憶體回收機制會不會回收單例

在Java1.2之前,記憶體回收行程有個bug,會造成當單例在沒有全域的引用時被當做垃圾清除。也就是說,如果一個單例只有本單例類引用它本身,那麼該單例就會被當做垃圾清除。

在Java1.2之後,這個bug已經被修正了,也不再需要一個全域引用來保護單例。所以對於1.2之後,單例是不會被回收的。1.2之前就需要小心了。

下面有篇文章專門討論這個問題:http://blog.csdn.net/zhengzhb/article/details/7331354

2、反射破壞單例原則

public class Test {public static void main(String[] args) throws NoSuchMethodException,SecurityException, InstantiationException, IllegalAccessException,IllegalArgumentException, InvocationTargetException {Singleton regSingleton1 = Singleton.getInstance();Singleton regSingleton2 = Singleton.getInstance();Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(new Class[] {});constructor.setAccessible(true);Singleton regSingleton3=constructor.newInstance(null);System.out.println(regSingleton1==regSingleton2);System.out.println(regSingleton1==regSingleton3);}}

列印結果為

true

false

可以說明,反射產生了一個新的執行個體

3、不同的類載入器載入,會得到多個執行個體

4、序列化會產生多個執行個體

5、使用ThreadLocal為每個線程產生一個單例

比如在JavaWeb中,需要為每個線程產生一個session,各個session之間互不影響,這裡就可以使用ThreadLocal,來為每個線程產生session

public class Singleton {private  static ThreadLocal<Singleton> thread_local=new ThreadLocal<Singleton>();private Singleton() {}public static Singleton getInstance() {if(thread_local.get()==null){thread_local.set(new Singleton());}return thread_local.get();}}

測試下:

public class Test {public static void main(String[] args) throws NoSuchMethodException,SecurityException, InstantiationException, IllegalAccessException,IllegalArgumentException, InvocationTargetException {final Singleton singleton1=Singleton.getInstance();Singleton singleton2=Singleton.getInstance();System.out.println(singleton1==singleton2);new Thread(){public void run() {Singleton singleton=Singleton.getInstance();System.out.println(singleton==singleton1);};}.start();}}

列印結果為:

true

false

說明,每個線程都會拿到一個執行個體的副本,不一樣的單例執行個體。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.