Java設計模式學習--單例模式
阿新 • • 發佈:2018-12-15
單例模式
核心作用 : 保證一個類只有一個例項 , 並且提供一個訪問該實習的全域性訪問點 .
常見的應用場景 :
- Windows 的 Tsask Manager (工作管理員) 就是典型的單例模式 當你開啟工作管理員的時候,只要你能不關閉工作管理員頁面,你電腦只能顯示一個工作管理員頁面 。
- 專案中 , 讀取配置檔案的類 , 一般也只有一個物件。 沒有必要每次使用配置檔案資料,每次 new 一個物件去讀取。
- 資料庫連線池的設計一般也是採用單例模式 , 因為資料庫連線是一種資料庫資源。
- 在 Spring 中 , 每個 Bean 預設就是單例的 , 這樣做的優點是 Spring 容器可以管理 。
- 在 Spring MVC 框架愛/struts1 框架中 , 控制器物件也是單例的 。
單例模式有什麼優點呢?
- 由於單例模式只生成一個例項,減少了系統性能開銷 , 當一個物件的產生需要比較多的資源時 , 如 讀取配置 , 產生其他依賴物件時 , 則可以通過在應用啟動時直接產生一個單例物件, 然後永久駐留記憶體的放式解決 。
- 單例模式可以在系統設定全域性的訪問點 , 優化環共享資源訪問 , 例如 可以設計一個單例類, 負責所有資料表的對映處理 。
常見單例模式的四種實現放式
1), 餓漢式單例模式
/** * 餓漢式單例模式 * * 當類初始化的時候,進行建立當前類的例項 執行緒安全 沒有延遲載入 */ public class SingletionDemo { //1 宣告一個私有的靜態變數 private static SingletionDemo instance=new SingletionDemo(); //2 私有化構造器 避免外部直接建立物件 private SingletionDemo(){} //3 建立一個對外的公共的靜態方法 訪問該變數 //方法沒有同步 效率高 public static SingletionDemo getInstance() { return instance; } }
餓漢式單例模式總結如下:
- 在程式碼上, static 變數會在類裝載時初始化 , 此時也不會涉及多個執行緒訪問該物件的問題 。虛擬機器保證只會裝載一次該類 , 肯定不會發生併發訪問的問題 。 因此, 可以省略Synchronized 關鍵字 。
- 也存在這樣一個問題: 如果只是載入本類 , 而不是呼叫getInstance() , 甚至永遠沒有呼叫 ,則會造成資源浪費 !
2),懶漢式單例模式
/** 懶漢式 單例模式
* @author 曉電腦
*
* 單執行緒狀態下是隻是一個例項物件 如果多個執行緒訪問則會出現問題
* 多執行緒則會是多個物件 需要加上synchronized
*/
public class SingletionDemo01 {
//1 宣告一個私有的靜態變數
private static SingletionDemo01 instance;
//2 私有構造器 避免外部直接建立物件
private SingletionDemo01(){}
//3 建立一個對外的公共的靜態方法 訪問該變數 如果變數沒有物件 建立物件
public static SingletionDemo01 getInstance() throws InterruptedException {
if (null == instance){
instance=new SingletionDemo01();
}
return instance;
}
}
如果我們使用main方法進行測試如下
public static void main (String[] args) throws InterruptedException {
System.out.println("_________單執行緒狀態下_________");
System.out.println(SingletionDemo01.getInstance());
System.out.println(SingletionDemo01.getInstance());
}
main是一個單執行緒, 所以 上面列印的倆個地址是一樣的 , 如果模擬多執行緒的情況下,則會出現問題 ,我們改動懶漢單例模式如下
/** 懶漢式 單例模式
* @author 曉電腦
*
* 單執行緒狀態下是隻是一個例項物件 如果多個執行緒訪問則會出現問題
* 多執行緒則會是多個物件 需要加上synchronized
*/
public class SingletionDemo01 {
//1 宣告一個私有的靜態變數
private static SingletionDemo01 instance;
//2 私有構造器 避免外部直接建立物件
private SingletionDemo01(){}
//3 建立一個對外的公共的靜態方法 訪問該變數 如果變數沒有物件 建立物件
//需要在static 後面加上synchronized
public static SingletionDemo01 getInstance(Long time) throws InterruptedException {
if (null == instance){
// 加入延遲時間 time
Thread.sleep(time);
instance=new SingletionDemo01();
}
return instance;
}
}
接下來建立一個執行緒,對懶漢單例模式進行訪問
/**
* 建立一個執行緒 來執行懶漢單例模式
*/
static class ThreadDemo extends Thread{
private Long time;
//構造器對延遲時間進行初始化
public ThreadDemo(Long time){
this.time=time;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "---->" + SingletionDemo01.getInstance(time));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
main方法進行測試多執行緒情況下訪問懶漢單例模式
public static void main (String[] args) throws ClassNotFoundException, InterruptedException {
System.out.println("_________單執行緒狀態下_________");
// System.out.println(SingletionDemo01.getInstance());
// System.out.println(SingletionDemo01.getInstance());
System.out.println("______模擬多執行緒_______");
//設定延遲時間
ThreadDemo demo = new ThreadDemo(100L);
//設定執行緒名稱
demo.setName("zs");
//啟動執行緒
demo.start();
ThreadDemo demo1 = new ThreadDemo(300L);
demo1.setName("www");
demo1.start();
}
執行結果如下
總結 :
- 懶漢單例模式,需要加上 Synchronized 同步鎖 ,才能再多執行緒的情況下也是同一個物件,否則只是單執行緒下是同一個物件而多執行緒情況下,是不同的物件 。
- Lazy load ! 延遲載入, 懶載入 ! 真正用的時候才載入 !
- 也有一個問題: 資源利用率高了 。 但是 , 每次呼叫getInstance()方法都要同步,併發效率較低 。
3),靜態內部類實現放式(也是一種懶載入放式)
/**靜態內部類實現方式
* @author 曉電腦
*/
public class StaticSingletion {
//1 私有化構造器 避免外部類直接建立物件
private StaticSingletion(){
}
//2 建立static 內部類
private static class DemoStatic{
private static final StaticSingletion instance=new StaticSingletion();
}
//3 提供外部訪問的入口
public static StaticSingletion getInstance(){
return DemoStatic.instance;
}
}
總結 :
- 外部類沒有 static 屬性 ,則不會像餓漢式那樣立即載入物件 。
- 只有真正呼叫了 getInstance() 方法,才會載入靜態內部類 。載入類時是執行緒安全的 。 instance 是static final 型別 ,保證了記憶體中只有這樣的一個例項存在 ,而且 只能被賦值一次 ,從而保證了執行緒安全性 。
- 兼備了併發高效呼叫和延遲載入的優勢 。
4),使用列舉的方式實現單例模式
package com.tuogo.instance;
/**
* 使用列舉來實現單例
*/
public enum EnumSingletion {
INSTANCE;
//1 建立私有屬性
private EnumSingletionDemo02 instance;
//2 私有構造器 類載入的時候給這個屬性賦值
private EnumSingletion(){
instance=new EnumSingletionDemo02();
}
//3 提供外界訪問的入口
public EnumSingletionDemo02 getInstance() {
return instance;
}
}
/**要實現單例的類
*/
class EnumSingletionDemo02{
}
main測試類程式碼 如下
public static void main (String[] args) {
EnumSingletionDemo02 instance1 = EnumSingletion.INSTANCE.getInstance();
EnumSingletionDemo02 instance2 = EnumSingletion.INSTANCE.getInstance();
EnumSingletionDemo02 instance3= EnumSingletion.INSTANCE.getInstance();
System.out.println(instance1);
System.out.println(instance2);
System.out.println(instance3);
}
總結:
- 實現簡單
- 列舉本身就是單例模式。 由JVM從根本上提供保障! 避免了通過反射和反序列化的漏洞!
- 缺點: 無延遲載入
5),雙重檢測鎖實現 (不推薦使用)
- 因為 由於編譯器優化原因和JVM底層內部模型原因偶爾會出現問題 , 不建議使用 。
- 這裡就不做具體的展現
那我們如何選用適合哪一種單例模式呢?
- 單例物件 佔用 資源少 , 不需要 延遲載入 : 列舉類 好與 餓漢式
- 單例物件 佔用 資源大 , 需要 延遲載入 : 靜態內部類式單利模式 好與 懶漢式
拓展
IDEA怎麼生成UML類圖?(也就是對於已有的程式碼,想將相關類繪製成UML類圖)
第一步
File -> Setting->Tools -> JAVA class Diagrams
第二步
第三步生成類圖
在你想生成類圖的類右鍵 -> Diagrams -> Show Diagram...
如圖
這時你可以左鍵類名不丟,也可以把類拖進來