Java 设计模式(一)之单例模式 理论代码相结合
今天就讓我們拿Java的單例模式開篇吧,持續更新中。
讓我們一起學習設計模式吧,說它是基礎也是基礎,說它不是,又確實不是。它穿插在各處。學好它也是為了能讓自己更進一步。
很喜歡一句話:”八小時內謀生活,八小時外謀發展“
地點:😂我也不知道
作者:L
Java 設計模式之單例模式
- 一、前言
- 概念:
- 應用:
- 實現單例模式的原則和過程:
- 方式:
- 二、單例模式
- 2.1、餓漢式(靜態常量)
- 2.2、餓漢式(靜態代碼塊)
- 2.3、懶漢式(線程不安全)
- 2.4、懶漢式(線程安全,同步方法)
- 2.5、懶漢式(線程并不安全的同步代碼塊)
- 2.6、懶漢式(雙重檢查)
- 2.7、懶漢式(靜態內部類)
- 2.8、枚舉類實現
- 三、單例在一些源碼中的使用
- · 3.1、JDK
- 3.2、Mybatis
- 四、自言自語
一、前言
概念:
單例模式,屬于創建類型的一種常用的軟件設計模式。通過單例模式的方法創建的類在當前進程中只有一個實例(根據需要,也有可能一個線程中屬于單例,如:僅線程上下文內使用同一個實例)–來自百度百科
應用:
單例模式可以讓我們只創建一個對象從而避免了頻繁創建對象導致的內存消耗和垃圾回收。
單例模式只允許創建一個對象,因此節省內存,加快對象訪問速度,因此對象需要被公用的場合適合使用,如多個模塊使用同一個數據源連接對象等等。如:
1.需要頻繁實例化然后銷毀的對象。
2.創建對象時耗時過多或者耗資源過多,但又經常用到的對象。
3.有狀態的工具類對象。
4.頻繁訪問數據庫或文件的對象。
項目中的具體應用:
實現單例模式的原則和過程:
1.單例模式:確保一個類只有一個實例,自行實例化并向系統提供這個實例
2.單例模式分類:餓單例模式(類加載時實例化一個對象給自己的引用),懶單例模式(調用取得實例的方法如getInstance時才會實例化對象)
3.單例模式要素:
a.私有構造方法
b.私有靜態引用指向自己實例
c.以自己實例為返回值的公有靜態方法
方式:
單例模式有八種方式:
二、單例模式
2.1、餓漢式(靜態常量)
代碼實現:
package com.crush.singleton01;/*** 單例模式* @Author: crush* @Date: 2021-08-06 9:14* version 1.0*/ public class SingletonTest1 {public static void main(String[] args) {// 獲取兩次,看獲取到的對象 確實是單例的嗎Singleton singleton = Singleton.getInstance();Singleton singleton1 = Singleton.getInstance();System.out.println(singleton == singleton1);System.out.println("singleton hashcode:"+singleton.hashCode());System.out.println("singleton1 hashcode:"+singleton1.hashCode());/*** 輸出:* true* singleton hashcode:24324022* singleton1 hashcode:24324022*/} }/*** 1、餓漢式(靜態常量)代碼實現*/ class Singleton{/*** 構造器私有化*/private Singleton(){};/** * 在類的內部創建一個對象實例 隨當前類加載而加載 沒有線程安全問題。 */private final static Singleton INSTANCE=new Singleton();/*** 再提供一個 公有的方法來返回這個靜態常量*/public static Singleton getInstance(){return INSTANCE;} }結論及優缺:
優點:餓漢式(靜態常量方式)它不用擔心線程安全問題,它本身就是在類的裝載的時候完成實例化的。
缺點:但是缺點也在這個地方,不管用不用,只要類裝載了,那么他就會直接完成實例化,不能達到懶加載的效果,如果你從始至終都沒有使用過這個類,那么就會造成內存的浪費。
注意:你可能會想類都已經加載了,為什么我還會不用到呢?
在單例模式中大都調用getInstance方法,getInstance這個靜態方法可以讓類加載,那么同樣的道理,如果這個類中還有其他的靜態方法,你調用它的時候,同樣會使類進行裝載。
小結:這種單例方式,其實還是可以用的,至少不用擔心線程同步問題,那種使用特別頻繁的類用這種方式還是沒啥問題的,除了可能會導致內存浪費,
2.2、餓漢式(靜態代碼塊)
public class SingletonTest1 {public static void main(String[] args) {// 我們去拿兩次,看獲取到的對象 確實是單例的嗎Singleton singleton = Singleton.getInstance();Singleton singleton1 = Singleton.getInstance();System.out.println(singleton == singleton1);System.out.println("singleton hashcode:" + singleton.hashCode());System.out.println("singleton1 hashcode:" + singleton1.hashCode());/*** 輸出:* true* singleton hashcode:24324022* singleton1 hashcode:24324022*/} }/*** 1、餓漢式(靜態代碼塊)代碼實現*/ class Singleton {/** * 構造器私有化 */private Singleton() {}/** * 在類的內部創建一個對象實例 隨當前類加載而加載 沒有線程安全問題。*/private static Singleton singleton;static {//改為在靜態代碼塊中 創建單例對象singleton = new Singleton();}/** * 再提供一個 公有的方法來返回這個靜態常量 */public static Singleton getInstance() {return singleton;} }這種方式其實和第一種非常類似,只是將類的實例化過程放進靜態代碼塊而已。優缺點同餓漢式(靜態常量)。
2.3、懶漢式(線程不安全)
public class SingletonTest3 {public static void main(String[] args) {//懶漢式 線程不安全方式,適合單線程使用//===========單線程下=========== // Singleton singleton = Singleton.getInstance(); // Singleton singleton1 = Singleton.getInstance(); // // System.out.println(singleton == singleton1); // // System.out.println("singleton hashcode:" + singleton.hashCode()); // System.out.println("singleton1 hashcode:" + singleton1.hashCode());/***true 單線程下是安全的。* singleton hashcode:24324022* singleton1 hashcode:24324022*/// =========模擬多線程下=============Runnable runnable = new Runnable(){@Overridepublic void run() {Singleton instance = Singleton.getInstance();System.out.println(instance.hashCode());}};Runnable runnable2 = new Runnable(){@Overridepublic void run() {Singleton instance1 = Singleton.getInstance();System.out.println(instance1.hashCode());}};Thread thread1 = new Thread(runnable);Thread thread2 = new Thread(runnable2);thread1.start();thread2.start();/*** 結果并不唯一,* 可能會出現相同,也有可能不同,多測幾次,就能發現是線程不安全的。* 94433* 21648409*/} }/*** 懶漢式 線程不安全方式*/ class Singleton {/*** 構造器私有化*/private Singleton() {}/*** 在類的內部創建一個對象實例 隨當前類加載而加載 沒有線程安全問題。*/private static Singleton singleton;/*** 提供一個公有的方法* 當使用到這個方法時,才去創建singleton*/public static Singleton getInstance() {if(singleton==null){// 通過在這里堵賽的方式來模擬多線程try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}singleton= new Singleton();}return singleton;} }結論及優缺:
2.4、懶漢式(線程安全,同步方法)
public class SingletonTest4 {public static void main(String[] args) {//懶漢式 線程不安全方式,適合單線程使用//===========單線程下=========== // Singleton singleton = Singleton.getInstance(); // Singleton singleton1 = Singleton.getInstance(); // // System.out.println(singleton == singleton1); // // System.out.println("singleton hashcode:" + singleton.hashCode()); // System.out.println("singleton1 hashcode:" + singleton1.hashCode());/***true 單線程下是安全的。* singleton hashcode:24324022* singleton1 hashcode:24324022*/// =========模擬多線程下=============Runnable runnable = new Runnable(){@Overridepublic void run() {Singleton instance = Singleton.getInstance();System.out.println(instance.hashCode());}};Runnable runnable2 = new Runnable(){@Overridepublic void run() {Singleton instance1 = Singleton.getInstance();System.out.println(instance1.hashCode());}};Thread thread1 = new Thread(runnable);Thread thread2 = new Thread(runnable2);thread1.start();thread2.start();/**69027346902734*/} }/*** 2、懶漢式 線程安全方式*/ class Singleton {/*** 構造器私有化*/private Singleton() {}/*** 在類的內部創建一個對象實例 隨當前類加載而加載 沒有線程安全問題。*/private static Singleton singleton;/*** 提供一個公有的方法* 當使用到這個方法時,才去創建singleton* 加上 synchronized 關鍵字 解決線程安全問題*/public static synchronized Singleton getInstance() {if(singleton==null){// 通過在這里堵賽的方式來模擬多線程try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}singleton= new Singleton();}return singleton;} }結論及優缺:
- 其實代碼和懶漢式線程不安全的實現,就是在Singleton getInstance()上多了一個了synchronized,將這個方法變成了同步方法,解決了線程同步問題。
- 缺點:但是因為在方法上加了synchronized 關鍵字,導致執行效率的降低。并且之后每次來獲取,都要進行同步,但其實本質上這段代碼執行一次,之后都是retrun 是最佳的,而方法進行同步就大大降低效率拉。
- 不推薦這種方式,雖然做到線程同步,但效率太低,影響使用。
2.5、懶漢式(線程并不安全的同步代碼塊)
public class SingletonTest5 {public static void main(String[] args) {//懶漢式 線程不安全方式,適合單線程使用//===========單線程下=========== // Singleton singleton = Singleton.getInstance(); // Singleton singleton1 = Singleton.getInstance(); // // System.out.println(singleton == singleton1); // // System.out.println("singleton hashcode:" + singleton.hashCode()); // System.out.println("singleton1 hashcode:" + singleton1.hashCode());/***true 單線程下是安全的。* singleton hashcode:24324022* singleton1 hashcode:24324022*/// =========模擬多線程下=============Runnable runnable = new Runnable() {@Overridepublic void run() {Singleton instance = Singleton.getInstance();System.out.println(instance.hashCode());}};Runnable runnable2 = new Runnable() {@Overridepublic void run() {Singleton instance1 = Singleton.getInstance();System.out.println(instance1.hashCode());}};Thread thread1 = new Thread(runnable);Thread thread2 = new Thread(runnable2);thread1.start();thread2.start();/*** 如果這樣的話 多線程是并不安全的。207757185987586*/} }/*** 2、懶漢式 網上有說這是線程安全的問題,也有說不是的*/ class Singleton {/*** 構造器私有化*/private Singleton() {}/*** 在類的內部創建一個對象實例 隨當前類加載而加載 沒有線程安全問題。*/private static Singleton singleton;/*** 提供一個公有的方法* 當使用到這個方法時,才去創建singleton* 在同步代碼塊 處添加 synchronized*/public static Singleton getInstance() {if (singleton == null) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (Singleton.class) {singleton = new Singleton();}}return singleton;} }結論及優缺:
1)其實本意是對上一種方式做一個優化,想要提高同步效率,改為這種同步代碼塊的方式
2)但實際上,這并不能起到線程同步的作用,跟上一種方式遇到的問題是一樣的。
3)同樣不建議采取這種方式來實現單例模式。
2.6、懶漢式(雙重檢查)
public class SingletonTest6 {public static void main(String[] args) {//懶漢式 線程安全方式,適合單、多線程使用//===========單線程下=========== // Singleton singleton = Singleton.getInstance(); // Singleton singleton1 = Singleton.getInstance(); // // System.out.println(singleton == singleton1); // // System.out.println("singleton hashcode:" + singleton.hashCode()); // System.out.println("singleton1 hashcode:" + singleton1.hashCode());/***true 單線程下是安全的。* singleton hashcode:24324022* singleton1 hashcode:24324022*/// =========模擬多線程下=============Runnable runnable = new Runnable() {@Overridepublic void run() {Singleton instance = Singleton.getInstance();System.out.println(instance.hashCode());}};Runnable runnable2 = new Runnable() {@Overridepublic void run() {Singleton instance1 = Singleton.getInstance();System.out.println(instance1.hashCode());}};Thread thread1 = new Thread(runnable);Thread thread2 = new Thread(runnable2);thread1.start();thread2.start();/*** 線程安全* 7739563* 7739563*/} }/*** 2、懶漢式 雙重檢查 線程安全*/ class Singleton {/*** 構造器私有化*/private Singleton() {}/*** 在類的內部創建一個對象實例 隨當前類加載而加載 沒有線程安全問題。*/private static Singleton singleton;/*** 提供一個公有的方法* 當使用到這個方法時,才去創建singleton* 在同步代碼塊 處添加 synchronized* 雙重檢查*/public static Singleton getInstance() {if (singleton == null) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (Singleton.class) {if(singleton==null){singleton = new Singleton();}}}return singleton;} }結論及優缺:
1)我們在代碼中進行了兩次if (singleton == null)操作,一次在同步代碼塊外,一次在同步代碼塊內,兩次檢查,保證了線程的安全。
2)這樣的話,同步代碼只要執行一次,singleton也只需實例化一次,既做到了懶加載,又同時保證線程安全,提高了執行效率。
3)結論:懶加載、線程安全、效率較高,當然用這種方式啊。
2.7、懶漢式(靜態內部類)
public class SingletonTest7 {public static void main(String[] args) {//懶漢式 線程安全方式,適合單、多線程使用//===========單線程下=========== // Singleton singleton = Singleton.getInstance(); // Singleton singleton1 = Singleton.getInstance(); // // System.out.println(singleton == singleton1); // // System.out.println("singleton hashcode:" + singleton.hashCode()); // System.out.println("singleton1 hashcode:" + singleton1.hashCode());/***true 單線程下是安全的。* singleton hashcode:24324022* singleton1 hashcode:24324022*/// =========模擬多線程下=============Runnable runnable = new Runnable() {@Overridepublic void run() {Singleton instance = Singleton.getInstance();System.out.println(instance.hashCode());}};Runnable runnable2 = new Runnable() {@Overridepublic void run() {Singleton instance1 = Singleton.getInstance();System.out.println(instance1.hashCode());}};Thread thread1 = new Thread(runnable);Thread thread2 = new Thread(runnable2);thread1.start();thread2.start();/*** 線程安全* 7739563* 7739563*/} }/*** 2、懶漢式 靜態內部類方式*/ class Singleton {/*** 構造器私有化*/private Singleton() {}/** * 寫一個靜態內部類,然后在類的內部再寫一個靜態常量 Singleton */private static class SingletonInstance {private final static Singleton SINGLETON=new Singleton();}/*** 提供一個公有的方法* 當使用到這個方法時,才去加載 SingletonInstance 獲得 Singleton 實例 */public static Singleton getInstance() {return SingletonInstance.SINGLETON;} }結論及優缺:
1)這種方式同樣不會產生線程同步問題,也是借用JVM的類裝載的機制來保證實例化的時候只有一個線程。
2)靜態內部類SingletonInstance在Singleton被裝載時,并不會立即實例化,而是在需要實例化的時候,調用了getInstance 方法,才會進行 SingletonInstance類的裝載。
3)類的靜態屬性只會在第一次加載類的時候進行初始化,而在這里,JVM的類裝載機制幫助我們保證了線程安全性。
4)小結:避免了線程安全問題、利用了靜態內部類延遲加載(做到懶加載)、效率高,這不更爽了嗎,用起來。
2.8、枚舉類實現
/*** 單例模式** @Author: crush* @Date: 2021-08-06 9:14* version 1.0*/ public class SingletonTest8 {public static void main(String[] args) {Singleton singleton1 = Singleton.SINGLETON;Singleton singleton2 = Singleton.SINGLETON;System.out.println(singleton1 == singleton2);System.out.println(singleton1.hashCode());System.out.println(singleton2.hashCode());} }/*** 2、 枚舉方式*/ enum Singleton {SINGLETON; }結論及優缺:
- 沒有多線程問題,效率高,能夠防止反序列化重新創建對象,也是推薦使用的方式。
注:一般來說,大都采用餓漢式,并竟方便,如果十分在意資源的話,一般采用靜態內部類。但是一切的使用,都是具體問題具體分析,沒有最好的,只有最適合的。
三、單例在一些源碼中的使用
· 3.1、JDK
像JDK中的 Runtime就是使用了餓漢式單例方式實現
3.2、Mybatis
Mybatis中 ErrorContext為ThreadLocal基于線程唯一
Spring中也有蠻多哈,沒有一一去找了。
四、自言自語
你卷我卷,大家卷,什么時候這條路才是個頭啊。
有時候也想停下來歇一歇,一直做一個事情,感覺挺難堅持的。
你好,我是博主寧在春。
如果你正巧看到這篇文章,并且覺得對你有益的話,就給個贊吧,讓我感受一下分享的喜悅吧,蟹蟹。
如若有寫的有誤的地方,請不嗇賜教!!
同樣如若有存在疑惑的地方,請留言或私信,定會在第一a時間回復你。
持續更新中
總結
以上是生活随笔為你收集整理的Java 设计模式(一)之单例模式 理论代码相结合的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SpringBoot日志收集-Aop方式
- 下一篇: JUC系列(二)回顾Synchroniz