設(shè)計模式之單例模式

單例設(shè)計模式理解起來非常簡單甘桑。一個類只允許創(chuàng)建一個對象(或者實例),那這個類就是一個單例類歹叮,這種設(shè)計模式就叫單例模式跑杭。

使用場景

處理資源訪問沖突

下面的示例中如果每個類都創(chuàng)建一個 Logger 實例,就可能造成日志內(nèi)容被覆蓋的情況咆耿。

public class Logger {
  private FileWriter writer;

  public Logger() {
    File file = new File("log.txt");
    writer = new FileWriter(file, true); //true表示追加寫入
  }

  public void log(String message) {
    writer.write(mesasge);
  }
}


public class UserController {
  private Logger logger = new Logger();

  public void login(String username, String password) {
    // ...省略業(yè)務(wù)邏輯代碼...
    logger.log(username + " logined!");
  }
}

public class OrderController {
  private Logger logger = new Logger();

  public void create(OrderVo order) {
    // ...省略業(yè)務(wù)邏輯代碼...
    logger.log("Created an order: " + order.toString());
  }
}

表示全局唯一類

如果有些數(shù)據(jù)在系統(tǒng)中只應(yīng)保存一份德谅,那就比較適合設(shè)計為單例類。比如萨螺,配置信息類窄做,全局 ID 生成器等愧驱。

如何實現(xiàn)一個單例?

要實現(xiàn)一個單例椭盏,我們要考慮以下幾點:

  • 構(gòu)造函數(shù)需要是 private 訪問權(quán)限的组砚,這樣才能避免外部通過 new 創(chuàng)建實例;
  • 考慮對象創(chuàng)建時的線程安全問題掏颊;
  • 考慮是否支持延遲加載惫确;
  • 考慮 getInstance() 性能是否高(是否加鎖)。

餓漢式

public class Singleton {
  private static final Singleton instance = new Singleton();

  private Singleton() {}

  public static Singleton getInstance() {
    return instance;
  }
}

懶漢式

懶漢式相對于餓漢式的優(yōu)勢是支持延遲加載蚯舱。但缺點也很明顯改化,因為使用了synchronized關(guān)鍵字導(dǎo)致這個方法的并發(fā)度很低。如果這個單例類偶爾會被用到枉昏,那這種實現(xiàn)方式還可以接受陈肛。但是,如果頻繁地用到兄裂,就會導(dǎo)致性能瓶頸句旱,這種實現(xiàn)方式就不可取了。

public class Singleton {
  private static Singleton instance;

  private Singleton() {}

  public static synchronized Singleton getInstance() {
    if (instance == null) {
      instance = new Singleton();
    }
    return instance;
  }
}

雙重檢測

這是一種既支持延遲加載晰奖、又支持高并發(fā)的單例實現(xiàn)方式谈撒。

public class Singleton {
  private static Singleton instance;

  private Singleton() {}

  public static Singleton getInstance() {
    if (instance == null) {
      synchronized(Singleton.class) { // 此處為類級別的鎖
        if (instance == null) {
          instance = new Singleton();
        }
      }
    }
    return instance;
  }
}

在 java1.5 以下instance = new Singleton();有指令重排問題,需要給instance成員變量加上volatile關(guān)鍵字匾南,java1.5 之后不會再這個有問題啃匿。

靜態(tài)內(nèi)部類

這種方式利用了 Java 的靜態(tài)內(nèi)部類,有點類似餓漢式蛆楞,但又能做到了延遲加載溯乒。

當(dāng)外部類 Singleton 被加載的時候,并不會創(chuàng)建 SingletonHolder 實例對象豹爹。只有當(dāng)調(diào)用 getInstance() 方法時裆悄,SingletonHolder 才會被加載,這個時候才會創(chuàng)建 instance臂聋。insance 的唯一性光稼、創(chuàng)建過程的線程安全性,都由 JVM 來保證孩等。所以艾君,這種實現(xiàn)方法既保證了線程安全,又能做到延遲加載瞎访。

public class Singleton {
  private Singleton() {}

  private static class SingletonHolder{
    private static final Singleton instance = new Singleton();
  }

  public static Singleton getInstance() {
    return SingletonHolder.instance;
  }
}

枚舉

這是一種最簡單的實現(xiàn)方式腻贰,基于枚舉類型的單例實現(xiàn)。這種實現(xiàn)方式通過 Java 枚舉類型本身的特性扒秸,保證了實例創(chuàng)建的線程安全性和實例的唯一性播演。

public enum IdGenerator {
  INSTANCE;
  private AtomicLong id = new AtomicLong(0);

  public long getId() {
    return id.incrementAndGet();
  }
}

如何實現(xiàn)線程唯一的單例冀瓦?

上面的單例類對象是進程唯一的,一個進程只能有一個單例對象写烤。那如何實現(xiàn)一個線程唯一的單例呢翼闽?

假設(shè) IdGenerator 是一個線程唯一的單例類。在線程 A 內(nèi)洲炊,我們可以創(chuàng)建一個單例對象 a感局。因為線程內(nèi)唯一,在線程 A 內(nèi)就不能再創(chuàng)建新的 IdGenerator 對象了暂衡,而線程間可以不唯一询微,所以,在另外一個線程 B 內(nèi)狂巢,我們還可以重新創(chuàng)建一個新的單例對象 b撑毛。

我們通過一個 ConcurrentHashMap 來存儲對象,其中 key 是線程 ID唧领,value 是對象藻雌。這樣我們就可以做到,不同的線程對應(yīng)不同的對象斩个,同一個線程只能對應(yīng)一個對象胯杭。實際上,Java 語言本身提供了 ThreadLocal 工具類受啥,可以更加輕松地實現(xiàn)線程唯一單例做个。

public class IdGenerator {
  private AtomicLong id = new AtomicLong(0);

  private static final ConcurrentHashMap<Long, IdGenerator> instances
          = new ConcurrentHashMap<>();

  private IdGenerator() {}

  public static IdGenerator getInstance() {
    Long currentThreadId = Thread.currentThread().getId();
    instances.putIfAbsent(currentThreadId, new IdGenerator());
    return instances.get(currentThreadId);
  }

  public long getId() {
    return id.incrementAndGet();
  }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市腔呜,隨后出現(xiàn)的幾起案子叁温,更是在濱河造成了極大的恐慌,老刑警劉巖核畴,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異冲九,居然都是意外死亡谤草,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門莺奸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來丑孩,“玉大人,你說我怎么就攤上這事灭贷∥卵В” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵甚疟,是天一觀的道長仗岖。 經(jīng)常有香客問我逃延,道長,這世上最難降的妖魔是什么轧拄? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任揽祥,我火速辦了婚禮,結(jié)果婚禮上檩电,老公的妹妹穿的比我還像新娘拄丰。我一直安慰自己,他們只是感情好俐末,可當(dāng)我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布料按。 她就那樣靜靜地躺著,像睡著了一般卓箫。 火紅的嫁衣襯著肌膚如雪站绪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天丽柿,我揣著相機與錄音恢准,去河邊找鬼。 笑死甫题,一個胖子當(dāng)著我的面吹牛馁筐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播坠非,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼敏沉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了炎码?” 一聲冷哼從身側(cè)響起盟迟,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎潦闲,沒想到半個月后攒菠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡歉闰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年辖众,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片和敬。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡凹炸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出昼弟,到底是詐尸還是另有隱情啤它,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站变骡,受9級特大地震影響离赫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜锣光,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一笆怠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧誊爹,春花似錦蹬刷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至搂漠,卻和暖如春迂卢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背桐汤。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工而克, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人怔毛。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓员萍,卻偏偏與公主長得像,于是被迫代替她去往敵國和親拣度。 傳聞我的和親對象是個殘疾皇子碎绎,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容