這篇文章是今天重寫的,開會搞了一下午,另一篇文章寫了一半,等晚上寫完明天發。最近在使用一個科學的方法研究一個有意思的事,如何科學化的判斷你的另一半是否也愛你。廢話不多說了,開始一下今天的文章。
單例模式是一種最常見的設計模式,寫法也比較多,在這篇文章裡面主要是對單例模式的各種寫法進行一個介紹。面試的時候會對其中兩三種進行體會,而且我還遇到了口述單例模式的例子。重要性就不言而喻了吧。
一、單例模式的介紹
單例模式的重要點在於兩個,一個是在哪些地方使用到了單例模式,一個是單例模式如何寫。之前只考慮到了如何寫,但是哪些地方使用到了,表述的不是很清楚。這一次我找了幾個實際例子。
概念:單例模式確保某個類只有一個實例。
有一個通俗的理解,那就是在古代,全國就一個皇帝。如何確保一個皇帝?這就是單例模式。
先看如何寫,然後再看在哪用。
二、單例模式的各種寫法
1、懶漢式:基本寫法
懶漢式就是什麼時候用,什麼時候創建類的實例。
<code>public
class
Singleton
{private
Singleton
() {}private
static
Singleton single=null
;public
static
SingletongetInstance
() {if
(single ==null
) { single =new
Singleton(); }return
single; } } /<code>
特點:
(1)線程不安全(併發時可能出現多個單例)
(2)構造方法為private,限定了外部只能從getInstance去獲取單例
(3)使用static關鍵字,表明全局只有一份節約了資源,但是如果單例對象比較複雜,new時就比較耗時間。這一點要注意。
上面最主要的缺點就是線程不安全,因此想要解決這個問題,只需要對方法加鎖即可。
2、懶漢式:使用synchronized 同步
<code>public
class
Singleton
{private
static
Singleton instance;private
Singleton
()
{}public
static
synchronized
SingletongetInstance
()
{if
(instance ==null
) { instance =new
Singleton(); }return
instance; } } /<code>
特點:
(1)此時線程安全
(2)因為加了鎖,此時如果單例對象複雜,不僅耗內存,而且new的時間更長,效率更低。
但是上面這種效率不高還有另外一個原因。一個線程過來想要創建單例,首先要進行synchronized鎖判斷,接下來判斷單例是否為空,如果為空,那就創建單例。
既然上面每個線程過來都需要鎖判斷和單例是否為空的判斷,這樣做有點耗時,畢竟鎖判斷是比較耗時的。
3、懶漢式:雙重檢查鎖定
為了解決上面的這個問題,才有了雙重檢查鎖定。
<code>public
class
Singleton
{private
static
Singleton instance;private
Singleton
()
{}public
static
SingletongetInstance
()
{if
(singleton ==null
) {synchronized
(Singleton.
class
) {if
(singleton ==null
) { singleton =new
Singleton(); } } }return
singleton; } /<code>
特點:
(1)線程安全
(2)耗內存,而且單例對象比較複雜,比較耗時,但是對單重鎖效率提升不少。
此時為了減少鎖的判斷量,只需要對單例進行判斷即可,如果不為空直接返回,如果為空,那就創建新的單例。
4、餓漢式:基本寫法(instance為private)
惡漢式是一開始就先建好,用的時候直接返回即可。
<code>public
class
Singleton
{private
Singleton
()
{}private
static
final
Singleton instance =new
Singleton();public
static
SingletongetInstance
()
{return
instance; } } /<code>
特點:
(1)線程安全(因為提前創建了,所以是天生的線程安全)
(2)單例對象修飾為private,只能通過getInstance獲取。
此時單例因為有static修飾,因此在類加載的時候就會初始化,這對應用的啟動會造成一定程度的影響。
5、餓漢式:基本寫法(instance為public)
<code>public
class
Singleton
{public
static
final
Singleton instance =new
Singleton();private
Singleton
()
{} } /<code>
和上面沒什麼區別。
6、餓漢式:靜態代碼塊
<code>public
class
Singleton
{private
Singleton instance =null
;private
Singleton
() {}static
{ instance =new
Singleton(); }public
static
SingletongetInstance
() {return
this
.instance; } } /<code>
特點:
(1)線程安全
(2)類初始化時實例化 instance
7、靜態內部類
<code>public
class
Singleton
{private
static
class
InstanceHolder
{private
static
final
Singleton INSTANCE =new
Singleton(); }private
Singleton
()
{}public
static
final
SingletongetInstance
()
{return
InstanceHolder.INSTANCE; } } /<code>
特點:
(1)線程安全
(2)效率高,避免了synchronized帶來的性能影響
這種就好很多。很多大佬都推薦。
8、枚舉式
<code>public
enum
Singleton { INSTANCE;public
void
getInstance
() { System.out
.println("Do whatever you want"
); } } /<code>
特點:
(1)線程安全(枚舉類型默認就是安全的)
(2)避免反序列化破壞單例
枚舉類型更好,但是枚舉類型會造成更多的內存消耗。枚舉會比使用靜態變量多消耗兩倍的內存,如果是Android應用,儘量避免。原因的話,是因為枚舉類型會在編譯時轉化為一個類,會涉及很多複雜的操作,這裡就先不逼逼了。
9、CAS方式
<code>public
class
Singleton
{private
static
final AtomicReference INSTANCE =new
AtomicReference ();private
Singleton
() {}public
static
SingletongetInstance
() {for
(;;) { Singleton instance = INSTANCE.get
();if
(instance !=null
) {return
instance; } instance =new
Singleton();if
(INSTANCE.compareAndSet(null
, instance)) {return
instance; } } } } /<code>
特點:
(1)優點:不需要使用傳統的鎖機制來保證線程安全,CAS 是一種基於忙等待的算法,依賴底層硬件的實現,相對於鎖它沒有線程切換和阻塞的額外消耗,可以支持較大的並行度。
如果不理解CAS的話,可以看這篇文章:
java併發編程CAS機制原理分析(面試必問,學習中必會的一個知識點)
(2)缺點:如果忙等待一直執行不成功(一直在死循環中),會對 CPU 造成較大的執行開銷。而且,這種寫法如果有多個線程同時執行 singleton = new Singleton(); 也會比較耗費堆內存。
10、Lock機制
<code>public
class
Singleton
{private
static
Singleton instance =null
;private
static
Locklock
=new
ReentrantLock();private
Singleton
() {}public
static
SingletongetInstance
() {if
(instance ==null
) {lock
.lock
();if
(instance ==null
) { instance =new
Singleton(); }lock
.unlock(); }return
instance; } } /<code>
當然還有一些其他的實現單例的寫法,比如說登記式單例等等。
三、單例模式的使用
1.Windows的Task Manager(任務管理器)就是很典型的單例模式(這個很熟悉吧),想想看,是不是呢,你能打開兩個windows task manager嗎?不信你自己試試看哦~
- windows的Recycle Bin(回收站)也是典型的單例應用。在整個系統運行過程中,回收站一直維護著僅有的一個實例。
- 網站的計數器,一般也是採用單例模式實現,否則難以同步。
- 應用程序的日誌應用,一般都何用單例模式實現,這一般是由於共享的日誌文件一直處於打開狀態,因為只能有一個實例去操作,否則內容不好追加。
- Web應用的配置對象的讀取,一般也應用單例模式,這個是由於配置文件是共享的資源。
6.數據庫連接池的設計一般也是採用單例模式,因為數據庫連接是一種數據庫資源。數據庫軟件系統中使用數據庫連接池,主要是節省打開或者關閉數據庫連接所引起的效率損耗,這種效率上的損耗還是非常昂貴的,因為何用單例模式來維護,就可以大大降低這種損耗。
- 多線程的線程池的設計一般也是採用單例模式,這是由於線程池要方便對池中的線程進行控制。
- 操作系統的文件系統,也是大的單例模式實現的具體例子,一個操作系統只能有一個文件系統。
以上內容來源於簡書:我本來寫了鏈接,但是頭條等各個站點,不能包含,我會在評論區給出,尊重原創。
四、總結
有兩種場景可能導致非單例的情況
場景一:如果單例由不同的類加載器加載,那便有可能存在多個單例類的實例
場景二:如果 Singleton 實現了 java.io.Serializable 接口,那麼這個類的實例就可能被序列化和反序列化。
單例的寫法基本上就是這些,可能在不同的場景下使用不同的方式,對我來說,在後端更經常使用的就是枚舉類型,但是Android開發當中很少使用。