單例模式的核心作用
保證一個類只有一個實例,並且提供一個訪問該實例的全局訪問點。
單例模式的應用場景
Windows的Task Manager(任務管理器)。
Windows的Recycle Bin(回收站)。在整個系統運行過程中,回收站一直維護着僅有的一個實例。
項目中,讀取配置文件的類,一般也只有一個對象。沒有必要每次使用配置文件數據,都new一個對象去讀取。
網站的計數器,一般也是採用單例模式實現,否則難以同步。
應用程序的日誌應用,一般都使用單例模式實現,這一般是由於共享的日誌文件一直處於打開狀態,因爲只能有一個實例去操作,否則內容不好追加。
數據庫連接池的設計一般也是採用單例模式,因爲數據庫連接是一種數據庫資源。
操作系統的文件系統,也是大的單例模式實現的具體例子,一個操作系統只能有一個文件系統。
Application也是單例的典型應用。(Servlet編程中會涉及到)
在Spring中,每個Bean默認就是單例的,這樣做的優點是Spring容器可以管理。
在servlet編程中,每個servlet也是單例。
在spring MVC框架/struts框架中,控制器對象也是單例。
單例模式的優點
由於單例模式只生成一個實例,減少了系統性能開銷,當一個對象的產生需要比較多的資源時,如讀取配置、產生其他依賴對象時,則可以通過在應用啓動時直接產生一個單例對象,然後永久駐留內存的方式來解決。
單例模式可以在系統設置全局的訪問點,優化共享資源訪問,例如可以設計一個單例類,負責所有數據表的映射處理。
常見的單例模式(五種)
1.餓漢式:線程安全,調用頻率高。不能延遲加載。
2.懶漢式:線程安全,調用效率不高。可以延遲加載。
3.雙重檢測鎖式:由於JVM底層內部模型原因,偶爾會出現問題。不建議使用。
4.靜態內部類式:線程安全,調用效率高。可以延遲加載。
5.枚舉單例:線程安全,調用效率高,不能延遲加載。可以天然的防止反射和反序列化漏洞。
注:延遲加載:當在真正需要數據的時候,才真正執行數據加載操作。可以簡單理解爲,只有在使用時纔會發sql語句進行查詢。
單例模式----餓漢式實現:單例對象立即加載
public class SingletonDemo01 {
//類初始化時,立即加載這個對象(沒有延時加載的優勢)!由於加載類時,天然的是線程安全的!
private static SingletonDemo01 instance = new SingletonDemo01();
private SingletonDemo01(){
}
//方法沒有同步,調用效率高!
public static SingletonDemo01 getInstance(){
return instance;
}
}
餓漢式單例模式代碼中,static變量會在類加載時初始化,此時也不會涉及多個線程對象訪問該對象的問題。虛擬機保證只會裝載一次該類,肯定不會發生併發訪問的問題。因此,可以省略synchronized關鍵字。
存在的問題:如果只是加載本類,而不是要調用getInstance(),甚至永遠沒有調用,則會造成資源浪費。
單例模式----懶漢式實現:單例對象延遲加載
public class SingletonDemo02 {
//類初始化時,不初始化這個對象(延遲加載,真正用的時候再創建)。
private static SingletonDemo02 instance;
//私有化構造器
private SingletonDemo02(){
}
//方法同步,調用效率低!
public static synchronized SingletonDemo02 getInstance(){
if (instance == null){
instance = new SingletonDemo02();
}
return instance;
}
}
懶加載,延遲加載,只有真正用的時候才加載。
存在的問題:雖然資源利用率高了,但是每次調用getInstance()方法都要同步,併發效率較低。
單例模式----雙重檢測鎖實現
public class SingletonDemo03 {
private static volatile SingletonDemo03 instance = null;
public static SingletonDemo03 getInstance(){
if (instance == null){
SingletonDemo03 sc;
synchronized (SingletonDemo03.class){
sc = instance;
if (sc == null){
synchronized (SingletonDemo03.class){
if (sc == null){
sc = new SingletonDemo03();
}
}
instance = sc;
}
}
}
return instance;
}
private SingletonDemo03(){
}
}
這個模式將內容同步到下方if內部,提高了執行的效率,不必每次獲取對象都進行同步,只有第一次才同步,創建了以後就沒必要了。
存在的問題:由於編譯器優化原因和JVM底層內部模型原因,偶爾會出問題。不建議使用(實際工作中用不到)。
注意:在1.4以及更早版本的Java中,許多JVM對於volatile關鍵字的實現會導致雙重檢查加鎖的失效。如果使用的是舊版本的jdk,就不要用這個方法來實現單例模式。
單例模式----靜態內部類實現方式
public class SingletonDemo04 {
public static class SingletonClassInstance {
private static final SingletonDemo04 instance = new SingletonDemo04();
}
public static SingletonDemo04 getInstance() {
return SingletonClassInstance.instance;
}
private SingletonDemo04(){
}
}
外部類沒有static屬性,則不會像餓漢式那樣立即加載對象。
只有真正調用getInstance(),纔會加載靜態內部類。加載類時是線程安全的。instance是static final類型,保證了內存中只有這樣一個實例存在,而且只能被賦值一次,從而保證了線程安全性。
兼備了併發高效調用和延遲加載的優勢。
靜態內部類實現方式也是一種懶加載方式。使用較多。
單例模式----使用枚舉實現單例模式
public enum SingletonDemo05 {
//這個枚舉元素,本身就是單例對象!
INSTANCE;
//添加自己需要的操作
public void singletonOperation(){
}
public static Object INSTANCE() {
return INSTANCE;
}
}
使用枚舉實現單例模式,實現簡單。枚舉本身就是單例模式。由JVM從根本上提供保障。避免通過反射和反序列化的漏洞。
存在的問題:無延遲加載。
各種單例模式的適用場景
當單例對象佔用資源較少,不需要延遲加載的時候,枚舉實現單例模式優於餓漢式。
當單例對象佔用資源較大,需要延遲加載的時候,靜態內部類的實現方式優於懶漢式。
單例模式的類圖
getInstance()方法是靜態的,意味着它是一個類方法,所以可以在代碼的任何地方使用SingletonDemo01.getInstance()訪問它。和訪問全局變量一樣,只是這裏我們可以延遲實例化。(類圖是由idea自帶的工具生成的)
以上爲單例模式的學習筆記,此文章爲尚學堂視頻的學習筆記+自己總結。