本篇文章主要介紹設計模式——單例模式詳解。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcsQXYtJ3bm9CXldWYtlWPzNXZj9mcw1ycz9WL49jb1c0Y1NmeOFTUU9ENVR1TtxGVaFTTqpFMNdFTykEVNBTR65EM4k3YsR2VZRHbyg1aGJjYzJEWkZHOXFWdVhUY6VzVZBHctxkeWJjWoFzVhRXUXlld4d0YxkTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
單例
介紹
單例模式(Singleton Pattern)是Java中最基礎最簡單的設計模式之一,這種模式屬于
建立型模式
,提供的就是一種建立對象的方式。這種模式中的單一類建立自己的對象,確定隻有一個對象被建立,并且為所有對象提供一個通路它的全局通路點。
單例模式用來解決頻繁建立與銷毀執行個體對象問題,當我們想要控制執行個體建立個數或者複用執行個體時,就可以使用單例模式,這樣有助于節省系統資源。
特點
- 單例類,顧名思義,隻有一個執行個體。
- 單例類必須是自己建立自己的唯一執行個體。
- 單例類必須給所有對象提供這個唯一執行個體。
- 構造函數私有。
應用場景
适用範圍
- 頻繁的通路資料庫或檔案的對象。
- 頻繁的需要執行個體化,然後銷毀的對象。
- 建立對象耗時長且耗資源,但是又需要常用的對象。
使用舉例
- Windows的任務管理器,每次隻能打開一個。
- 應用程式的日志應用。
- 網上線上人數統計。
- 配置檔案的通路類。
- 資料庫的連接配接池。
- 多線程的線程池。
- 作業系統的檔案系統。
設計思路
- 一個類每次都傳回唯一的對象(每次傳回都是同一個對象執行個體)。
- 提供一個擷取該執行個體的方法。(基本上都是靜态方法)
加載分類
-
:在應用開始時就建立單例執行個體。提前加載
-
:在延遲加載
首次被調用時才調用單例建立,即需要使用時,才加載建立單例對象執行個體。getInstance()方法
單例分類
1 餓漢式單例
- 類加載時就初始化好執行個體對象,容易産生垃圾對象,浪費記憶體。
- 無鎖,執行效率高。
- 天生線程安全,因為類加載時就已初始化好執行個體。
代碼
/**
* 餓漢式單例
* @author Andya
* @date 2021/3/9
*/
public class EHanSingleInstance {
//餓漢式:初始化時就建好執行個體
private static final EHanSingleInstance instance = new EHanSingleInstance();
private EHanSingleInstance() {
doSomething();
}
public static EHanSingleInstance getInstance() {
return instance;
}
public void doSomething() {
}
}
2 懶漢式單例
- 餓漢式屬于延遲加載初始化。
- 若不加鎖,則為非線程安全的。
- 若加鎖,則為線程安全的,但效率很低,基本上每次都要同步。
無鎖懶漢式代碼
/**
* 無鎖懶漢式單例,非線程安全
* @author Andya
* @date 2021/3/9
*/
public class LanHanNoLockSingleInstance {
//懶漢式:初始化時不建立執行個體,等需要時再建立
private static LanHanNoLockSingleInstance instance;
private LanHanNoLockSingleInstance() {
doSomething();
}
public static LanHanNoLockSingleInstance getInstance() {
//先判斷是否為空,若為空建立一個新的執行個體
if (instance == null) {
instance = new LanHanNoLockSingleInstance();
}
return instance;
}
public void doSomething() {
}
}
加鎖懶漢式代碼
/**
* 加鎖懶漢式單例,線程安全
* @author Andya
* @date 2021/3/9
*/
public class LanHanWithLockSingleInstance {
//懶漢式:初始化時不建立執行個體,等需要時再建立
private static LanHanWithLockSingleInstance instance;
private LanHanWithLockSingleInstance() {
doSomething();
}
public static synchronized LanHanWithLockSingleInstance getInstance() {
//先判斷是否為空,若為空建立一個新的執行個體
if (instance == null) {
instance = new LanHanWithLockSingleInstance();
}
return instance;
}
public void doSomething() {
}
}
3 雙重檢鎖式單例
在多線程應用中使用這種模式可以保證線程安全。因為如果執行個體為空,有可能存在兩個線程同時調用
getInstance()
方法的情況,這樣的話,第一個線程會首先使用新構造器執行個體化一個單例對象,但此時它還沒有完成單例對象的執行個體化操作,同時第二個線程也檢查到單例執行個體為空,也會開始執行個體化單例對象,這就造成了2次執行個體化對象。是以多線程應用中,需要
鎖
來檢查執行個體是否線程安全。
- 靜态方法鎖
public static synchronized Singleton getInstance()
- 代碼塊鎖
synchronized(SingleInstance.class){
if (instance == null) {
instance = new SingleInstance();
}
}
雖然加鎖可以保證線程安全,但是會帶來延遲,因為加鎖後,代碼塊在同一時刻隻能被一個線程執行,但是同步鎖隻有在執行個體沒被建立的時候才會起作用。如果單例執行個體已經被建立,其實不需要走該步驟。是以,我們可以在代碼塊鎖外面再加一層執行個體空判斷。
instance == null
被檢查
2
次。
if (instance == null) {
synchronized(SingleInstance.class){
if (instance == null) {
instance = new SingleInstance();
}
}
}
/**
* 雙檢鎖單例
* @author Andya
* @date 2021/3/9
*/
public class DoubleCheckSingleInstance {
//使用volatile保證多線程的可見性
private static volatile DoubleCheckSingleInstance instance;
private DoubleCheckSingleInstance() {
doSomething();
}
public static DoubleCheckSingleInstance getInstance() {
//第一次檢查是否建立過該單例
if (instance == null) {
//加鎖,保證線程安全
synchronized (DoubleCheckSingleInstance.class) {
if (instance == null) {
instance = new DoubleCheckSingleInstance();
}
}
}
return instance;
}
public void doSomething() {
}
}
4 靜态内部類單例
- 延遲加載初始化執行個體instance。
- 線程安全。
- 該方式隻适用于靜态域的情況。
- 該方式單例不會立即初始化,隻有顯式調用getInstance()方法時,才會顯式轉載靜态内部類,才會執行個體化instance。
/**
* 靜态内部類單例
* @author Andya
* @date 2021/3/9
*/
public class StaticInternalSingleInstance {
//靜态内部類
private static class StaticInternalSingleInstanceHolder{
private static final StaticInternalSingleInstance INSTANCE
= new StaticInternalSingleInstance();
}
private StaticInternalSingleInstance() {
doSomething();
}
public static final StaticInternalSingleInstance getInstance() {
return StaticInternalSingleInstanceHolder.INSTANCE;
}
public void doSomething() {
}
}
5 枚舉類單例
- 不屬于延遲加載初始化執行個體instance。
- 多線程安全。
- 支援序列化機制,進而防止多次執行個體化。
/**
* 枚舉型單例
* @author Andya
* @date 2021/3/9
*/
public enum EnumSingleInstance {
INSTANCE;
public EnumSingleInstance getInstance(){
return INSTANCE;
}
}
總結
一般在開發應用中,建議使用第1種餓漢式單例,而不推薦使用第2種懶漢式單例,除非明确需要延遲加載時,才會使用第4種靜态内部類單例,若涉及到反序列化建立對象時,推薦使用第5種枚舉式單例。其他也可以考慮使用第3種雙重檢鎖式單例。
燒不死的鳥就是鳳凰