開篇
設計模式對於很多小夥伴來說都是它認識你,但是你不認識它,設計模式可以幫助我們簡化代碼,提高代碼的複用率,減少代碼的耦合性,以及增加代碼的重複利用性,但是設計模式並非是好用的代言,有些時候也會給我們代來很多問題,比如簡單的判斷語句會變成複雜的多類關聯,也會引發一些安全問題,比如今天要說的單例模式。
基本介紹
單例模式(Singleton Pattern)是Java中最簡單的設計模式之一(暗藏玄機)。屬於創建模式之一,提供了一種創建對象的最佳方式。一個單一的類,負責創建自己的對象,同時保證只有單個對象被創建,對外提供唯一的方法來獲取該類的對象,並且不需要實例化對象。因此可以總結出以下幾點:
- 只能有一個實例(注意防止併發)。
- 實例必須是自己創建的。
- 必須給其他對象這一實例。
主要解決
一個被全局使用的類,被頻繁的創建,當你想控制資源,節省資源時,你就要使用單例模式。
關鍵代碼
- 默認構造函數要私有。
- 提供對外獲取對象的唯一方法。
使用場景
- Spring中Bean容器管理的對象,默認下就是通過單例構建對象
- 系統中ID生成器,通常採用單例模式創建出對象
- 比如定時任務中,發現任務和觸發執行器執行的類,一般都設計成單例模式獲取(頻繁使用)
缺點
- 沒有接口,不能被繼承
- 控制不好會有併發問題,除了枚舉類之外,可以被反射獲取新的實例
- 單一原則下,類應該只關心內部,不應該關心外部的創建,違背單一原則
具體實現並帶有優缺點分析
1、餓漢式
/**
* 餓漢式
* 利用classload上來初始化實例,解決併發問題
* 但是此方法比較大的問題就是,當初始化單利比較耗時,或者很久不會使用的時候,浪費內存
* 是最長用方案
*/
public class SingleObject {
//單例模式必然是上來就私有化
private SingleObject() {
}
//創建一個唯一的類對象
private static SingleObject instance = new SingleObject();
//提供一個獲取的方法
public static SingleObject getInstance() {
return instance;
}
}
2、懶漢式,不考慮併發
/**
* 因此引出懶漢式,節省內存
* 但是此情況又會出現線程併發問題
* 在判斷 instance == null 的時候可能出現併發,多線程同時到達new
*/
class SingleObjectLazy {
//依然是上來私有構造
private SingleObjectLazy() {
}
//創建靜態對象變量,不初始化
private static SingleObjectLazy instance;
//使用時在初始化
public static SingleObjectLazy getInstance() {
if (instance == null) {
instance = new SingleObjectLazy();
}
return instance;
}
}
3、懶漢式,加鎖
/**
* 由於併發引出加鎖,
* 但是很難出現同時獲取instance而且還是爲空的情況
* 一旦創建後,不在需要鎖住
* 此時就會造成效率低下
*/
class SingleObjectSyncLazy {
private SingleObjectSyncLazy() {
}
private static SingleObjectSyncLazy instance;
public static synchronized SingleObjectSyncLazy getInstance() {
if (instance == null) instance = new SingleObjectSyncLazy();
return instance;
}
}
4、懶漢式,雙重檢查代替方法鎖,提高效率,防止指令重排加入volatile
/**
* 使用雙重檢查機制
* 只有當併發null的時候才需要進入加鎖,此時併發的概率很小
* 還是有問題的,不加上 volatile修飾的話,會出指令重排
* 同時 synchronized 只保單線程的結果正確,不會保證指令重排,
*/
class SingleObjectSyncLazyDouble {
private SingleObjectSyncLazyDouble() {
}
//防止指令重排
private volatile static SingleObjectSyncLazyDouble instance;
public static SingleObjectSyncLazyDouble getInstance() {
if (instance == null) { //只有併發null的時候進入加鎖
synchronized (SingleObjectSyncLazyDouble.class) {
//as-if-serial synchronized 只會保證單線程內執行操作結果,指令依然會重排
if (instance == null) { //不加,就會直接創建兩個
/*此處會出現指令重排
* instance = new SingleObjectSyncLazyDouble();
* 1、開闢內存
* 2、實例化
* 3、引用指向
* 不加volatile 就會出現混亂效果,導致先引用,這時候第二線程剛好到達第一處檢查,
* 直接走人了。就出現了大問題
* */
instance = new SingleObjectSyncLazyDouble();
}
}
}
return instance;
}
}
5、懶加載、靜態內部類實現。
/**
* 靜態內部類的使用,靜態內部類內部的方法是在第一次調用的時候纔會加載,因此可以避免一上來就被初始化的問題
* 也是一種懶加載的形式
*/
class SingleObjectStaticLazy {
private SingleObjectStaticLazy() {
}
private static class StaticClass {
private static SingleObjectStaticLazy instance = new SingleObjectStaticLazy();
}
public static SingleObjectStaticLazy getInstance() {
return StaticClass.instance;
}
}
6、枚舉
以上都可以通過反射獲取新的實例,枚舉類被反射會直接觸發異常
該方法在1.5纔開始引入,不受衆,不過是小黃書推薦的方式。
/**
* 枚舉
*/
public enum SingleObject {
INSTANCE;
}
結束
以上就是單例模式創建的方式,優點缺點,希望可以對大家有所幫助 。