java單利模式與多線程總結歸納

来源:http://www.cnblogs.com/babycomeon/archive/2016/03/18/5284192.html
-Advertisement-
Play Games

在多線程的情況下,如何得到線程安全的單利模式?synchronized,內部靜態類,序列化與反序列化,靜態代碼塊,enum枚舉,多種方式總結


概念:
  java中單例模式是一種常見的設計模式,單例模式分三種:懶漢式單例、餓漢式單例、登記式單例三種。
  單例模式有一下特點:
  1、單例類只能有一個實例。
  2、單例類必須自己創建自己的唯一實例。
  3、單例類必須給所有其他對象提供這一實例。
  單例模式確保某個類只有一個實例,而且自行實例化並向整個系統提供這個實例。在電腦系統中,線程池、緩存、日誌對象、對話框、印表機、顯卡的驅動程式對象常被設計成單例。這些應用都或多或少具有資源管理器的功能。每台電腦可以有若幹個印表機,但只能有一個Printer Spooler,以避免兩個列印作業同時輸出到印表機中。每台電腦可以有若幹通信埠,系統應當集中管理這些通信埠,以避免一個通信埠同時被兩個請求同時調用。總之,選擇單例模式就是為了避免不一致狀態,避免政出多頭。

這裡主要詳細介紹兩種:懶漢式和餓漢式

一、立即載入/餓漢式

在調用方法前,實例就已經被創建,代碼:

 1 package com.weishiyao.learn.day8.singleton.ep1;
 2 
 3 public class MyObject {
 4     // 立即載入方式==惡漢模式
 5     private static MyObject myObject = new MyObject();
 6 
 7     private MyObject() {
 8     }
 9     
10     public static MyObject getInstance() {
11         // 此代碼版本為立即載入
12         // 此版本代碼的缺點是不能有其他實例變數
13         // 因為getInstance()方法沒有同步
14         // 所以有可能出現非線程安全的問題
15         return myObject;
16     }
17 }

 

創建線程類

1 package com.weishiyao.learn.day8.singleton.ep1;
2 
3 public class MyThread extends Thread {
4     @Override
5     public void run() {
6         System.out.println(MyObject.getInstance().hashCode());
7     }
8 }

創建運行類

 1 package com.weishiyao.learn.day8.singleton.ep1;
 2 
 3 public class Run {
 4     public static void main(String[] args) {
 5         MyThread t1 = new MyThread();
 6         MyThread t2 = new MyThread();
 7         MyThread t3 = new MyThread();
 8         t1.start();
 9         t2.start();
10         t3.start();
11     }
12 }

運行結果

1 167772895
2 167772895
3 167772895

hashCode是同一個值,說明對象也是同一個,說明實現了立即載入型的單利模式

二、延遲載入/懶漢式

在調用方法以後實例才會被創建,實現方案可以是將實例化放到無參構造函數當中,這樣只有當調用的時候才會創建對象的實例,代碼:

 1 package com.weishiyao.learn.day8.singleton.ep2;
 2 
 3 public class MyObject {
 4     private static MyObject myObject;
 5     
 6     private MyObject() {
 7         
 8     }
 9     
10     public static MyObject getInstance() {
11         // 延遲載入
12         if (myObject != null) {
13             
14         } else {
15             myObject = new MyObject();
16         }
17         return myObject;
18     }
19 }

創建線程類

1 package com.weishiyao.learn.day8.singleton.ep2;
2 
3 public class MyThread extends Thread {
4     @Override
5     public void run() {
6         System.out.println(MyObject.getInstance().hashCode());
7     }
8 }

創建運行類

package com.weishiyao.learn.day8.singleton.ep2;

public class Run {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();
    }
}

運行結果

1 167772895

這樣雖然取出了一個對象的實例,但是如果在多線程的環境中,就會出現多個實例的情況,這樣就不是單例模式了

運行測試類

 1 package com.weishiyao.learn.day8.singleton.ep2;
 2 
 3 public class Run {
 4     public static void main(String[] args) {
 5         MyThread t1 = new MyThread();
 6         MyThread t2 = new MyThread();
 7         MyThread t3 = new MyThread();
 8         MyThread t4 = new MyThread();
 9         MyThread t5 = new MyThread();
10         t1.start();
11         t2.start();
12         t3.start();
13         t4.start();
14         t5.start();
15     }
16 }

運行結果

1 980258163
2 1224717057
3 1851889404
4 188820504
5 1672864109

既然出現問題,就要解決問題,在懶漢模式中的多線程的解決方案,代碼:

第一種方案,最常見的,加synchronized,而synchronized可以加到不同的位置

第一種,方法鎖

 1 package com.weishiyao.learn.day8.singleton.ep3;
 2 
 3 public class MyObject {
 4     private static MyObject myObject;
 5     
 6     private MyObject() {
 7         
 8     }
 9     
10     synchronized public static MyObject getInstance() {
11         // 延遲載入
12         try {
13             if (myObject != null) {
14                 
15             } else {
16                 // 模擬在創建對象之前做一些準備性的工作
17                 Thread.sleep(2000);
19 myObject = new MyObject();
21 } 22 23 } catch (InterruptedException e) { 24 e.printStackTrace(); 25 } 26 return myObject; 27 } 28 }

這種synchronized的同步方案導致效率過於低下,整個方法都被鎖住

第二種synchronized使用方案

 1 package com.weishiyao.learn.day8.singleton.ep3;
 2 
 3 public class MyObject {
 4     private static MyObject myObject;
 5     
 6     private MyObject() {
 7         
 8     }
 9     
10     public static MyObject getInstance() {
11         // 延遲載入
12         try {
13             synchronized (MyObject.class) {
14                 if (myObject != null) {
15                     
16                 } else {
17                     // 模擬在創建對象之前做一些準備性的工作
18                     Thread.sleep(2000);
20 myObject = new MyObject();
22 } 23 } 24 25 26 } catch (InterruptedException e) { 27 e.printStackTrace(); 28 } 29 return myObject; 30 } 31 }

這種方法效率一樣很低,方法內的所有代碼都被鎖住,只需要鎖住關鍵代碼就好,第三種synchronized使用方案

 1 package com.weishiyao.learn.day8.singleton.ep3;
 2 
 3 public class MyObject {
 4     private static MyObject myObject;
 5     
 6     private MyObject() {
 7         
 8     }
 9     
10     public static MyObject getInstance() {
11         // 延遲載入
12         try {
13                 if (myObject != null) {
14                     
15                 } else {
16                     // 模擬在創建對象之前做一些準備性的工作
17                     Thread.sleep(2000);
18                     synchronized (MyObject.class) {
19                         myObject = new MyObject();
20                     }
21             }
22             
23             
24         } catch (InterruptedException e) {
25             e.printStackTrace();
26         }
27         return myObject;
28     }
29 }

這麼寫看似是最優方案了,但是,運行一下結果,發現,其實它是非線程安全的

結果:

1 1224717057
2 971173439
3 1851889404
4 1224717057
5 1672864109

Why?

雖然鎖住了對象創建的語句,每次只能有一個線程完成創建,但是,當第一個線程進來創建完成Object對象以後,第二個線程進來還是可以繼續創建的,因為我們緊緊只鎖住了創建語句,這個問題解決方案

 1 package com.weishiyao.learn.day8.singleton.ep3;
 2 
 3 public class MyObject {
 4     private static MyObject myObject;
 5     
 6     private MyObject() {
 7         
 8     }
 9     
10     public static MyObject getInstance() {
11         // 延遲載入
12         try {
13                 if (myObject != null) {
14                     
15                 } else {
16                     // 模擬在創建對象之前做一些準備性的工作
17                     Thread.sleep(2000);
18                     synchronized (MyObject.class) {
19                         if (myObject == null) {
20                             myObject = new MyObject();
21                         }
22                     }
23             }
24             
25             
26         } catch (InterruptedException e) {
27             e.printStackTrace();
28         }
29         return myObject;
30     }
31 }

只需要在鎖裡面再添加一個判斷,就可以保證單例了,這個是DCL雙檢查機制

結果如下:

1 1224717057
2 1224717057
3 1224717057
4 1224717057
5 1224717057

 三、使用內置靜態類實現單例

主要代碼

 1 package com.weishiyao.learn.day8.singleton.ep4;
 2 
 3 public class MyObject {
 4     // 內部類方式
 5     private static class MyObjectHandler {
 6         private static MyObject myObject = new MyObject();
 7     }
 8 
 9     public MyObject() {
10     }
11     
12     public static MyObject getInstance() {
13         return MyObjectHandler.myObject;
14     }
15 }

線程類代碼

1 package com.weishiyao.learn.day8.singleton.ep4;
2 
3 public class MyThread extends Thread {
4     @Override
5     public void run() {
6         System.out.println(MyObject.getInstance().hashCode());
7     }
8 }

運行類

 1 package com.weishiyao.learn.day8.singleton.ep4;
 2 
 3 public class Run {
 4     public static void main(String[] args) {
 5         MyThread t1 = new MyThread();
 6         MyThread t2 = new MyThread();
 7         MyThread t3 = new MyThread();
 8         MyThread t4 = new MyThread();
 9         MyThread t5 = new MyThread();
10         t1.start();
11         t2.start();
12         t3.start();
13         t4.start();
14         t5.start();
15     }
16 }

結果

1851889404
1851889404
1851889404
1851889404
1851889404

通過內部靜態類,得到了線程安全的單例模式

四、序列化和反序列化單例模式

內置靜態類可以達到線程安全的問題,但如果遇到序列化對象時,使用預設方式得到的結果還是多例的

MyObject代碼

package com.weishiyao.learn.day8.singleton.ep5;

import java.io.Serializable;

public class MyObject implements Serializable {
    
    /**
     * 
     */
    private static final long serialVersionUID = 888L;

    // 內部類方式
    private static class MyObjectHandler {
        private static MyObject myObject = new MyObject();
    }

    public MyObject() {
    }
    
    public static MyObject getInstance() {
        return MyObjectHandler.myObject;
    }
    
//    protected MyObject readResolve() {
//        System.out.println("調用了readResolve方法!");
//        return MyObjectHandler.myObject;
//    }
}

業務類

 1 package com.weishiyao.learn.day8.singleton.ep5;
 2 
 3 import java.io.File;
 4 import java.io.FileInputStream;
 5 import java.io.FileNotFoundException;
 6 import java.io.FileOutputStream;
 7 import java.io.IOException;
 8 import java.io.ObjectInputStream;
 9 import java.io.ObjectOutputStream;
10 
11 public class SaveAndRead {
12     public static void main(String[] args) {
13         try {
14             MyObject myObject = MyObject.getInstance();
15             FileOutputStream fosRef = new FileOutputStream(new File("myObjectFile.txt"));
16             ObjectOutputStream oosRef = new ObjectOutputStream(fosRef);
17             oosRef.writeObject(myObject);
18             oosRef.close();
19             fosRef.close();
20             System.out.println(myObject.hashCode());
21         } catch (FileNotFoundException e) {
22             e.printStackTrace();
23         } catch (IOException e) {
24             e.printStackTrace();
25         }
26         FileInputStream fisRef;
27         try {
28             fisRef = new FileInputStream(new File("myObjectFile.txt"));
29             ObjectInputStream iosRef = new ObjectInputStream(fisRef);
30             MyObject myObject = (MyObject) iosRef.readObject();
31             iosRef.close();
32             fisRef.close();
33             System.out.println(myObject.hashCode());
34         } catch (FileNotFoundException e) {
35             e.printStackTrace();
36         } catch (IOException e) {
37             e.printStackTrace();
38         } catch (ClassNotFoundException e) {
39             e.printStackTrace();
40         }
41         
42         
43     }
44 }

結果

1 970928725
2 1099149023

兩個不同的hashCode,證明並不是同一個對象,解決方案,添加下麵這段代碼

1   protected MyObject readResolve() {
2         System.out.println("調用了readResolve方法!");
3         return MyObjectHandler.myObject;
4     }

在反序列化的時候調用,可以得到同一個對象

1 System.out.println(myObject.readResolve().hashCode());

結果

1 1255301379
2 調用了readResolve方法!
3 1255301379

相同的hashCode,證明得到了同一個對象

五、使用static代碼塊實現單例

靜態代碼塊中的代碼在使用類的時候就已經執行了,所以可以應用靜態代碼快這個特性來實現單利模式

MyObject類

 1 package com.weishiyao.learn.day8.singleton.ep6;
 2 
 3 public class MyObject {
 4     private static MyObject instance = null;
 5 
 6     private MyObject() {
 7         super();
 8     }
 9     
10     static {
11         instance = new MyObject();
12     }
13     
14     public static MyObject getInstance() {
15         return instance;
16     }
17 }

線程類

 1 package com.weishiyao.learn.day8.singleton.ep6;
 2 
 3 public class MyThread extends Thread {
 4     @Override
 5     public void run() {
 6         for (int i = 0; i < 5; i++) {
 7             System.out.println(MyObject.getInstance().hashCode());
 8         }
 9     }
10 }

運行類

 1 package com.weishiyao.learn.day8.singleton.ep6;
 2 
 3 public class Run {
 4     public static void main(String[] args) {
 5         MyThread t1 = new MyThread();
 6         MyThread t2 = new MyThread();
 7         MyThread t3 = new MyThread();
 8         MyThread t4 = new MyThread();
 9         MyThread t5 = new MyThread();
10         t1.start();
11         t2.start();
12         t3.start();
13         t4.start();
14         t5.start();
15     }
16 }

運行結果:

 1 1678885403
 2 1678885403
 3 1678885403
 4 1678885403
 5 1678885403
 6 1678885403
 7 1678885403
 8 1678885403
 9 1678885403
10 1678885403
11 1678885403
12 1678885403
13 1678885403
14 1678885403
15 1678885403
16 1678885403
17 1678885403
18 1678885403
19 1678885403
20 1678885403
21 1678885403
22 1678885403
23 1678885403
24 1678885403
25 1678885403

通過靜態代碼塊只執行一次的特性也成功的得到了線程安全的單例模式

六、使用enum枚舉數據類型實現單例模式

枚舉enum和靜態代碼塊的特性類似,在使用枚舉時,構造方法會被自動調用,也可以用來實現單例模式

MyObject類

 1 package com.weishiyao.learn.day8.singleton.ep7;
 2 
 3 import java.sql.Connection;
 4 import java.sql.DriverManager;
 5 import java.sql.SQLException;
 6 
 7 
 8 public enum MyObject {
 9     connectionFactory;
10     
11     private Connection connection;
12     
13     private MyObject() {
14         try {
15             System.out.println("調用了MyObject的構造");
16             String url = "jdbc:mysql://172.16.221.19:3306/wechat_1?useUnicode=true&characterEncoding=UTF-8";
17             String name = "root";
18             String password = "111111";
19             String driverName = "com.mysql.jdbc.Driver";
20             Class.forName(driverName);
21             connection = DriverManager.getConnection(url, name, password);
22         } catch (ClassNotFoundException e) {
23             e.printStackTrace();
24         } catch (SQLException e) {
25             e.printStackTrace();
26         }
27     }
28     
29     public Connection getConnection() {
30         return connection;
31     }
32 }

線程類

 1 package com.weishiyao.learn.day8.singleton.ep7;
 2 
 3 public class MyThread extends Thread {
 4     @Override
 5     public void run() {
 6         for (int i = 0; i < 5; i++) {
 7             System.out.println(MyObject.connectionFactory.getConnection().hashCode());
 8         }
 9     }
10 }

運行類

 1 package com.weishiyao.learn.day8.singleton.ep7;
 2 
 3 public class Run {
 4     public static void main(String[] args) {
 5         MyThread t1 = new MyThread();
 6         MyThread t2 = new MyThread();
 7         MyThread t3 = new MyThread();
 8         MyThread t4 = new MyThread();
 9         MyThread t5 = new MyThread();
10         t1.start();
11         t2.start();
12         t3.start();
13         t4.start();
14         t5.start();
15     }
16 }

運行結果

 1 調用了MyObject的構造
 2 56823666
 3 56823666
 4 56823666
 5 56823666
 6 56823666
 7 56823666
 8 56823666
 9 56823666
10 56823666
11 56823666
12 56823666
13 56823666
14 56823666
15 56823666
16 56823666
17 56823666
18 56823666
19 56823666
20 56823666
21 56823666
22 56823666
23 56823666
24 56823666
25 56823666
26 56823666

上面這種寫法將枚舉類暴露了,違反了“職責單一原則”,可以使用一個類將枚舉包裹起來

 1 package com.weishiyao.learn.day8.singleton.ep8;
 2 
 3 import java.sql.Connection;
 4 import java.sql.DriverManager;
 5 import java.sql.SQLException;
 6 
 7 
 8 public class MyObject {
 9     
10     public enum MyEnumSingleton {
11         connectionFactory;
12         
13         private Connection connection;
14         
15         private MyEnumSingleton() {
16             try {
17                 System.out.println("調用了MyObject的構造");
18                 String url = "jdbc:mysql://172.16.221.19:3306/wechat_1?useUnicode=true&characterEncoding=UTF-8";
19                 String name = "root";
20                 String password = "111111";
21                 String driverName = "com.mysql.jdbc.Driver";
22                 Class.forName(driverName);
23                 connection = DriverManager.getConnection(url, name, password);
24             } catch (ClassNotFoundException e) {
25                 e.printStackTrace();
26             } catch (SQLException e) {
27                 e.printStackTrace();
28             }
29         }
30         
31         public Connection getConnection() {
32             return connection;
33         }
34     }
35     
36     public static Connection getConnection() {
37         return MyEnumSingleton.connectionFactory.getConnection();
38     }
39 }

更改線程代碼

 1 package com.weishiyao.learn.day8.singleton.ep8;
 2 
 3 public class MyThread extends Thread {
 4     @Override
 5     public void run() {
 6         for (int i = 0; i < 5; i++) {
 7             System.out.println(MyObject.getConnection().hashCode());
 8        

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • FFmpegInterop 是微軟推出的封裝 FFmpeg 的一個開源庫,旨在方便在 Windows 10、Windows 8.1 以及 Windows Phone 8.1 應用中使用 FFmpeg 進行媒體內容播放。FFmpegInterop 實現了一個 MediaStreamSource 以便通
  • 剛發現以前做的QQ郵箱發送郵件不能用了,查找了下原因:未加( EnableSsl SSL加密連接 )導致的,頓時覺得很坑,以前QQ郵箱也沒有這個限制啊,可能是最近加的。 好吧不多說直接從頭走起,照顧下新人 O(∩_∩)O~ 1、首先在QQ郵箱當中開啟“POP3/SMTP服務” <!--StartFr
  • 在前面的兩篇文章中,我們講到的都是如何將消息從server推向client,又或者是client再推向server,貌似這樣的邏輯沒什麼異常,但是放在真實 的環境中,你會很快發現有一個新需求,如何根據第三方系統的數據變化來即時的將新數據推送到各個客戶端,比如下麵這樣: ok,原理就是上面的這張圖,如
  • 還是看例子 會net都會看懂 不解釋
  • 學習asp.net mvc5心得
  • 在一個控制器中,它有很幾個視圖,在視圖中有一個菜單。點擊某一菜單,它分切換至對應的視圖,當前的視圖的菜單要高亮顯示。這個要求,也有許多網友問過Insus.NET。今天決定做一個例子,供大家學習與參考。先看看效果: 上圖中,當瀏覽China的視圖時,China這行菜單是Active的,其它沒有效果,依
  • <!doctype html> <html> <head> <title>PHP函數小展示</title> </head> <body> <?php //函數定義 function name(){ echo "這是一個無參函數"; } name();//無參函數調用 function name($a
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...