出自
> [Java:單例模式的七種寫法](http://www.blogjava.net/kenzhh/archive/2013/03/15/357824.html)
[TOC=1,2]
**第一種(懶漢,線程不安全):**
?1?public?class?Singleton?{??
?2?????private?static?Singleton?instance;??
?3?????private?Singleton?(){}???
?4?????public?static?Singleton?getInstance()?{??
?5?????if?(instance?==?null)?{??
?6?????????instance?=?new?Singleton();??
?7?????}??
?8?????return?instance;??
?9?????}??
10?}??
11?
這種寫法lazy loading很明顯,但是致命的是在多線程不能正常工作。
**第二種(懶漢,線程安全):
**
?1?public?class?Singleton?{??
?2?????private?static?Singleton?instance;??
?3?????private?Singleton?(){}
?4?????public?static?synchronized?Singleton?getInstance()?{??
?5?????if?(instance?==?null)?{??
?6?????????instance?=?new?Singleton();??
?7?????}??
?8?????return?instance;??
?9?????}??
10?}??
11?
這種寫法能夠在多線程中很好的工作,而且看起來它也具備很好的lazy loading,但是,遺憾的是,效率很低,99%情況下不需要同步。
**第三種(餓漢):**
1?public?class?Singleton?{??
2?????private?static?Singleton?instance?=?new?Singleton();??
3?????private?Singleton?(){}
4?????public?static?Singleton?getInstance()?{??
5?????return?instance;??
6?????}??
7?}??
8?
這種方式基于classloder機制避免了多線程的同步問題,不過,instance在類裝載時就實例化,雖然導致類裝載的原因有很多種,在單例模式中大多數都是調用getInstance方法,?但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載,這時候初始化instance顯然沒有達到lazy loading的效果。
**第四種(**餓**漢,變種):**
?1?public?class?Singleton?{??
?2?????private?Singleton?instance?=?null;??
?3?????static?{??
?4?????instance?=?new?Singleton();??
?5?????}??
?6?????private?Singleton?(){}
?7?????public?static?Singleton?getInstance()?{??
?8?????return?this.instance;??
?9?????}??
10?}??
11?
表面上看起來差別挺大,其實更第三種方式差不多,都是在類初始化即實例化instance。
**第五種(靜態內部類):
**
?1?public?class?Singleton?{??
?2?????private?static?class?SingletonHolder?{??
?3?????private?static?final?Singleton?INSTANCE?=?new?Singleton();??
?4?????}??
?5?????private?Singleton?(){}
?6?????public?static?final?Singleton?getInstance()?{??
?7?????????return?SingletonHolder.INSTANCE;??
?8?????}??
?9?}??
10?
這種方式同樣利用了classloder的機制來保證初始化instance時只有一個線程,它跟第三種和第四種方式不同的是(很細微的差別):第三種和第四種方式是只要Singleton類被裝載了,那么instance就會被實例化(沒有達到lazy loading效果),而這種方式是Singleton類被裝載了,instance不一定被初始化。因為SingletonHolder類沒有被主動使用,只有顯示通過調用getInstance方法時,才會顯示裝載SingletonHolder類,從而實例化instance。想象一下,如果實例化instance很消耗資源,我想讓他延遲加載,另外一方面,我不希望在Singleton類加載時就實例化,因為我不能確保Singleton類還可能在其他的地方被主動使用從而被加載,那么這個時候實例化instance顯然是不合適的。這個時候,這種方式相比第三和第四種方式就顯得很合理。
**第六種(枚舉):**
1?public?enum?Singleton?{??
2?????INSTANCE;??
3?????public?void?whateverMethod()?{??
4?????}??
5?}??
6?
這種方式是Effective Java作者Josh Bloch 提倡的方式,它不僅能避免多線程同步問題,而且還能防止反序列化重新創建新的對象,可謂是很堅強的壁壘啊,不過,個人認為由于1.5中才加入enum特性,用這種方式寫不免讓人感覺生疏,在實際工作中,我也很少看見有人這么寫過。
**第七種(雙重校驗鎖):
**
?1?public?class?Singleton?{??
?2?????private?volatile?static?Singleton?singleton;??
?3?????private?Singleton?(){}???
?4?????public?static?Singleton?getSingleton()?{??
?5?????if?(singleton?==?null)?{??
?6?????????synchronized?(Singleton.class)?{??
?7?????????if?(singleton?==?null)?{??
?8?????????????singleton?=?new?Singleton();??
?9?????????}??
10?????????}??
11?????}??
12?????return?singleton;??
13?????}??
14?}??
15?
這個是第二種方式的升級版,俗稱雙重檢查鎖定,詳細介紹請查看:[http://www.ibm.com/developerworks/cn/java/j-dcl.html](http://www.ibm.com/developerworks/cn/java/j-dcl.html)
在JDK1.5之后,雙重檢查鎖定才能夠正常達到單例效果。
**總結**
有兩個問題需要注意:
???? 1、如果單例由不同的類裝載器裝入,那便有可能存在多個單例類的實例。假定不是遠端存取,例如一些servlet容器對每個servlet使用完全不同的類? 裝載器,這樣的話如果有兩個servlet訪問一個單例類,它們就都會有各自的實例。
???? 2、如果Singleton實現了java.io.Serializable接口,那么這個類的實例就可能被序列化和復原。不管怎樣,如果你序列化一個單例類的對象,接下來復原多個那個對象,那你就會有多個單例類的實例。
對第一個問題修復的辦法是:
?1?private?static?Class?getClass(String?classname)??????
?2??????????????????????????????????????????throws?ClassNotFoundException?{?????
?3???????ClassLoader?classLoader?=?Thread.currentThread().getContextClassLoader();?????
?4???????
?5???????if(classLoader?==?null)?????
?6??????????classLoader?=?Singleton.class.getClassLoader();?????
?7???????
?8???????return?(classLoader.loadClass(classname));?????
?9????}?????
10?}??
11?
?對第二個問題修復的辦法是:?
?1?public?class?Singleton?implements?java.io.Serializable?{?????
?2????public?static?Singleton?INSTANCE?=?new?Singleton();?????
?3???????
?4????protected?Singleton()?{?????
?5?????????
?6????}?????
?7????private?Object?readResolve()?{?????
?8?????????????return?INSTANCE;?????
?9???????}????
10?}???
11?
對我來說,我比較喜歡第三種和第五種方式,簡單易懂,而且在JVM層實現了線程安全(如果不是多個類加載器環境),一般的情況下,我會使用第三種方式,只有在要明確實現lazy loading效果時才會使用第五種方式,另外,如果涉及到反序列化創建對象時我會試著使用枚舉的方式來實現單例,不過,我一直會保證我的程序是線程安全的,而且我永遠不會使用第一種和第二種方式,如果有其他特殊的需求,我可能會使用第七種方式,畢竟,JDK1.5已經沒有雙重檢查鎖定的問題了。
========================================================================
?[superheizai](http://superheizai.javaeye.com/)同學總結的很到位:
不過一般來說,第一種不算單例,第四種和第三種就是一種,如果算的話,第五種也可以分開寫了。所以說,一般單例都是五種寫法。懶漢,惡漢,雙重校驗鎖,枚舉和靜態內部類。
我很高興有這樣的讀者,一起共勉。
- JVM
- 深入理解Java內存模型
- 深入理解Java內存模型(一)——基礎
- 深入理解Java內存模型(二)——重排序
- 深入理解Java內存模型(三)——順序一致性
- 深入理解Java內存模型(四)——volatile
- 深入理解Java內存模型(五)——鎖
- 深入理解Java內存模型(六)——final
- 深入理解Java內存模型(七)——總結
- Java內存模型
- Java內存模型2
- 堆內內存還是堆外內存?
- JVM內存配置詳解
- Java內存分配全面淺析
- 深入Java核心 Java內存分配原理精講
- jvm常量池
- JVM調優總結
- JVM調優總結(一)-- 一些概念
- JVM調優總結(二)-一些概念
- VM調優總結(三)-基本垃圾回收算法
- JVM調優總結(四)-垃圾回收面臨的問題
- JVM調優總結(五)-分代垃圾回收詳述1
- JVM調優總結(六)-分代垃圾回收詳述2
- JVM調優總結(七)-典型配置舉例1
- JVM調優總結(八)-典型配置舉例2
- JVM調優總結(九)-新一代的垃圾回收算法
- JVM調優總結(十)-調優方法
- 基礎
- Java 征途:行者的地圖
- Java程序員應該知道的10個面向對象理論
- Java泛型總結
- 序列化與反序列化
- 通過反編譯深入理解Java String及intern
- android 加固防止反編譯-重新打包
- volatile
- 正確使用 Volatile 變量
- 異常
- 深入理解java異常處理機制
- Java異常處理的10個最佳實踐
- Java異常處理手冊和最佳實踐
- Java提高篇——對象克隆(復制)
- Java中如何克隆集合——ArrayList和HashSet深拷貝
- Java中hashCode的作用
- Java提高篇之hashCode
- 常見正則表達式
- 類
- 理解java類加載器以及ClassLoader類
- 深入探討 Java 類加載器
- 類加載器的工作原理
- java反射
- 集合
- HashMap的工作原理
- ConcurrentHashMap之實現細節
- java.util.concurrent 之ConcurrentHashMap 源碼分析
- HashMap的實現原理和底層數據結構
- 線程
- 關于Java并發編程的總結和思考
- 40個Java多線程問題總結
- Java中的多線程你只要看這一篇就夠了
- Java多線程干貨系列(1):Java多線程基礎
- Java非阻塞算法簡介
- Java并發的四種風味:Thread、Executor、ForkJoin和Actor
- Java中不同的并發實現的性能比較
- JAVA CAS原理深度分析
- 多個線程之間共享數據的方式
- Java并發編程
- Java并發編程(1):可重入內置鎖
- Java并發編程(2):線程中斷(含代碼)
- Java并發編程(3):線程掛起、恢復與終止的正確方法(含代碼)
- Java并發編程(4):守護線程與線程阻塞的四種情況
- Java并發編程(5):volatile變量修飾符—意料之外的問題(含代碼)
- Java并發編程(6):Runnable和Thread實現多線程的區別(含代碼)
- Java并發編程(7):使用synchronized獲取互斥鎖的幾點說明
- Java并發編程(8):多線程環境中安全使用集合API(含代碼)
- Java并發編程(9):死鎖(含代碼)
- Java并發編程(10):使用wait/notify/notifyAll實現線程間通信的幾點重要說明
- java并發編程-II
- Java多線程基礎:進程和線程之由來
- Java并發編程:如何創建線程?
- Java并發編程:Thread類的使用
- Java并發編程:synchronized
- Java并發編程:Lock
- Java并發編程:volatile關鍵字解析
- Java并發編程:深入剖析ThreadLocal
- Java并發編程:CountDownLatch、CyclicBarrier和Semaphore
- Java并發編程:線程間協作的兩種方式:wait、notify、notifyAll和Condition
- Synchronized與Lock
- JVM底層又是如何實現synchronized的
- Java synchronized詳解
- synchronized 與 Lock 的那點事
- 深入研究 Java Synchronize 和 Lock 的區別與用法
- JAVA編程中的鎖機制詳解
- Java中的鎖
- TreadLocal
- 深入JDK源碼之ThreadLocal類
- 聊一聊ThreadLocal
- ThreadLocal
- ThreadLocal的內存泄露
- 多線程設計模式
- Java多線程編程中Future模式的詳解
- 原子操作(CAS)
- [譯]Java中Wait、Sleep和Yield方法的區別
- 線程池
- 如何合理地估算線程池大小?
- JAVA線程池中隊列與池大小的關系
- Java四種線程池的使用
- 深入理解Java之線程池
- java并發編程III
- Java 8并發工具包漫游指南
- 聊聊并發
- 聊聊并發(一)——深入分析Volatile的實現原理
- 聊聊并發(二)——Java SE1.6中的Synchronized
- 文件
- 網絡
- index
- 內存文章索引
- 基礎文章索引
- 線程文章索引
- 網絡文章索引
- IOC
- 設計模式文章索引
- 面試
- Java常量池詳解之一道比較蛋疼的面試題
- 近5年133個Java面試問題列表
- Java工程師成神之路
- Java字符串問題Top10
- 設計模式
- Java:單例模式的七種寫法
- Java 利用枚舉實現單例模式
- 常用jar
- HttpClient和HtmlUnit的比較總結
- IO
- NIO
- NIO入門
- 注解
- Java Annotation認知(包括框架圖、詳細介紹、示例說明)