設計模式_單例模式
A.概述
1.概述
單例模式,是整個設計模式中最簡單的,也是開發中最常用的
有些對象,我們只需要一個,比如:線程池,緩存,對話框等
如果被實例化多次,會導致程序出現很多問題
比如:程序行爲異常,資源使用過量,或者結果不一致
2.與全局變量的區別
只創建一次對象,我們也可以直接在成員位置創建對象
也就是全局變量,我們也經常使用
但如果是全局變量,那麼程序已開始運行,就要創建對象
如果在項目中,這個對象非常消耗資源,而這次執行中又沒有使用,那不就很浪費資源
而單利模式不同,只有在使用時,才被調用,並且只創建一次
因此,單例模式確保一個類只有一個實例,並提供一個全局的訪問點
3.單例模式分類
a.同步getInstance():單例模式之懶漢式
b.雙重檢查鎖:單例模式之懶漢式
c.急切實例化:單例模式之餓漢式
4.下面就以一個巧克力工廠類來演示這三種模式
B.同步getInstance()
1.需求分析
巧克力的製作,需要計算機控制鍋爐
將牛奶和巧克力融在一起,然後送到下一階段
現在用代碼模擬巧克力鍋爐
package org.xxxx.oop.singleton; public class ChocolateBoiler { // 空的和煮過的 private boolean empty; private boolean boiled; // 代碼開始,鍋爐是空的,也沒煮過 public ChocolateBoiler() { empty = true; boiled = false; } // 給鍋爐添加原料 public void fill() { // 必須是空的才能添加 if (isEmpty()) { empty = false; // 鍋爐滿了 boiled = false; // 但還沒煮 } } // 開始煮 public void boil() { // 判斷必須是滿的,並且煮了 if (!isEmpty() && isBoiled()) { boiled = true; // 煮了 } } // 排除 public void drain() { // 判斷不爲空,並且煮了 if (!isEmpty() && isBoiled()) { empty = true; // 排空 } } // 判斷鍋爐是否爲空 public boolean isEmpty() { return empty; } // 判斷是否煮過 public boolean isBoiled() { return boiled; } }
這就是一個完整的操作過程,看上去邏輯也很嚴謹
但我們想一下,如果存在兩個實例,會發生什麼樣的事
一個鍋爐只能是一個對象,因此,我們就要引入單例模式
2.初識單例模式
在每次創建對象時,判斷是否存在,如果存在直接返回
只展示修改的代碼,其他方法不變,參考上一段代碼
package org.xxxx.oop.singleton; public class ChocolateBoiler { // 全局變量 private static ChocolateBoiler cb = null; // 私有構造,不能通過外部創建對象 private ChocolateBoiler() { } // 創建方法,實例化 public static ChocolateBoiler getInstance() { // 判斷是否爲空 if (cb == null) { cb = new ChocolateBoiler(); } // 返回該對象 return cb; } }
實例化問題解決了,我們再分析一下
一個工廠,不可能只有一個鍋爐,會有很多
這麼多鍋爐也不是一個一個工作,肯定是同時工作
這就要考慮多線程,在【JavaSE學習筆記】多線程01這一部分中
引入了窗口賣票這一例子,多個窗口同時售票
會出現同一張票被多個窗口售出,餘票爲負數
線程不安全,爲了使線程安全,因此就引入了同步機制
3.同步getInstance()代碼
package org.xxxx.oop.singleton; public class ChocolateBoiler { // 全局變量 private static ChocolateBoiler cb = null; // 私有構造,不能通過外部創建對象 private ChocolateBoiler() { } // 創建方法,實例化 增加synchronized關鍵字 public static synchronized ChocolateBoiler getInstance() { // 判斷是否爲空 if (cb == null) { cb = new ChocolateBoiler(); } // 返回該對象 return cb; } }
通過增加synchronized關鍵字,迫使每個線程在進入這個方法之前
都必須等到別的線程離開這個方法,也就是說,兩個線程不能同時出現在該方法中
4.缺點
這個方法比較簡單有效,但同步機制可能會使程序執行效率下降100倍
如果使用頻繁的話,就得重新考慮
同步鎖會降低性能,當然對於不考慮性能的程序來說,無關緊要
C.雙重檢查鎖
1.分析
對於同步Instance()來說,會降低程序的性能
我們考慮下,只有在線程第一次進入這個方法時才需要同步
也就是說,當對象被創建時,就不需要再去管同步了2.volatile關鍵字
首先檢查對象是否被實例化,如果沒有,再進行同步
這就要引入volatile關鍵字,用來確保將變量的更新操作通知到其他線程
在兩個或者更多的線程訪問的成員變量上使用volatile
當要訪問的變量已在synchronized代碼塊中,或者爲常量時,不必使用
當把變量聲明爲volatile類型後,編譯器與運行時都會注意到這個變量是共享的
因此不會將該變量上的操作與其他內存操作一起重排序
volatile變量不會被緩存在寄存器或者對其他處理器不可見的地方
因此在讀取volatile類型的變量時總會返回最新寫入的值。
在訪問volatile變量時不會執行加鎖操作,因此也就不會使執行線程阻塞
因此volatile變量是一種比sychronized關鍵字更輕量級的同步機制
3.當一個變量被volatile定義的特性
a.保證此變量對所有的線程的可見性,這裏的"可見性"
可見性:當一個線程修改了這個變量的值,volatile保證了新值能立即同步到主內存,以及每次使用前立即從主內存刷新
b.禁止指令重排序優化
4.雙重檢查鎖代碼
這樣,當只有第一次創建對象時,纔會進入同步機制package org.xxxx.oop.singleton; public class ChocolateBoiler { // 全局變量,用volatile修飾 private volatile static ChocolateBoiler cb = null; // 私有構造,不能通過外部創建對象 private ChocolateBoiler() { } // 創建方法,實例化 增加synchronized關鍵字 public static ChocolateBoiler getInstance() { // 先判斷是否爲空 if (cb == null) { // 對象不存在,開啓同步鎖 synchronized (ChocolateBoiler.class) { // 進入同步機制後,再次判斷一下是否存在 if (cb == null) { // 不存在,創建對象 cb = new ChocolateBoiler(); } } } // 返回該對象 return cb; } }
因此,大大的減少了getInstance()的時間消耗
5.缺點
在JDK1.4以及更低的版本,本方法不適用
D.急切實例化
1.概述
上述的兩種單例模式方法
同步機制:性能過低
雙重檢查鎖:不適用於舊版本
在此介紹急切實例化,也就是餓漢式,開發中常用,也比較簡單
2.急切實例化代碼
如果程序中總是要創建並使用單利模式
或者在創建和運行時的負擔不太繁重,可以使用該方法
保證了在任何線程訪問靜態變量之前,一定先被實例化package org.xxxx.oop.singleton; public class ChocolateBoiler { // 全局變量,直接創建對象,靜態初始化器,只執行一次,保證了線程安全、對象唯一 private static ChocolateBoiler cb = new ChocolateBoiler(); // 私有構造,不能通過外部創建對象 private ChocolateBoiler() { } // 創建方法 public static ChocolateBoiler getInstance() { // 返回該對象,對象已經創建了,直接返回 return cb; } }