GOF23设计模式(创建型模式)单例模式
目錄:
一:單例模式的核心作用、常見應用場景
二:五種單例模式及其實現(xiàn)
三:關于反射和反序列化破解單例模式的漏洞,以及相應的解決方案
四:測試五種單例模式的效率
一:核心作用及常見應用場景:
核心作用:保證一個類只有一個實例對象,并且提供一個訪問該實例對象的全局訪問點(獲取實例對象的方法),通過該訪問點獲得對象唯一
常見應用場景:
| 1 | Windows的 Task Manager(任務管理器)就是很典型的單例模式(無論打開多少次任務管理器,只會出現(xiàn)一個任務管理器) |
| 2 | windows的 Recycle Bin(回收站)也是典型的單例應用。在整個系統(tǒng)運行過程中,回收站直維護著僅有的一個實例 |
| 3 | 項目中,讀取配置文件的類,一般也只有一個對象。沒有必要每次使用配置文件數(shù)據(jù),每次new一個對象去讀取。 |
| 4 | 網(wǎng)站的計數(shù)器,一般也是采用單例模式實現(xiàn),否則難以同步。 |
| 5 | 應用程序的日志應用,一般都何用單例模式實現(xiàn),這一般是由于共享的日志文件一直處于打開狀態(tài),因為只能有一個實例去操作,否則內(nèi)容不好追加。 |
| 6 | 數(shù)據(jù)庫連接池的設計一般也是采用單例模式,因為數(shù)據(jù)庫連接是一種數(shù)據(jù)庫資源 |
| 7 | 操作系統(tǒng)的文件系統(tǒng),也是大的單例模式實現(xiàn)的具體例子,一個操作系統(tǒng)只能有一個文件系統(tǒng)。 |
| 8 | Application也是單例的典型應用( Servlet編程中會涉及到) |
| 9 | 在 Spring中,每個Bean默認就是單例的,這樣做的優(yōu)點是 Spring容器可以管理 |
| 10 | 在 servlet編程中,每個Sere也是單例 |
| 11 | 在 spring MVC框架/ struts1框架中,控制器對象也是單例 |
二:五種單例模式及其實現(xiàn)方式~~(附代碼)
| 主要 | 餓漢式(線程安全,調(diào)用效率高。但是,不能延時加載。) 懶漢式(線程安全,調(diào)用效率不高。但是,可以延時加載。) |
| 其他 | 雙重檢測鎖式(由于JVM底層內(nèi)部模型原因,偶爾會出問題。不建議使用) 靜態(tài)內(nèi)部類式(線程安全,調(diào)用效率高。但是,可以延時加載) 枚舉單例(線程安全,調(diào)用效率高,不能延時加載) |
1. 餓漢式:
餓漢式單例模式代碼中, static變量會在類裝載時初始化,此時也不會涉及多個線程對象訪問該對象的問題。虛擬機保證只會裝載一次該類,肯定不會發(fā)生并發(fā)訪問的問題。因此,可以省略synchronized關鍵字。
問題:如果只是加載本類,而不是要調(diào)用 getinstance(),甚至永遠沒有調(diào)用,則會造成資源浪費!
package 設計模式;/*** 測試餓漢式單例模式* 線程安全* 立即加載*/
public class Singleton1 {//類初始化時立即加載(沒有延時加載的優(yōu)勢),加載類時,天然是線程安全的public static Singleton1 instance = new Singleton1();//構(gòu)造器私有private Singleton1() {}//方法沒有同步,調(diào)用效率高public static Singleton1 getInstance() {return instance;}public static void main(String[] args) {Singleton1 singleton1 = Singleton1.getInstance();Singleton1 singleton2 = singleton1.getInstance();System.out.println(singleton1 == singleton2);//true}
}
2.? 懶漢式
要點: lazy load 延遲加載,只有真正用的時候才加載!
問題: 資源利用率高了。但是,每次調(diào)用 getinstanceO方法都要同步,并發(fā)效率較低。
package 設計模式;/*** 測試懶漢式單例模式* 線程安全* 延時加載*/
public class Singleton2 {//類初始化時,不初始化這個對象(延時加載,真正用的時候才加載)public static Singleton2 instance;//構(gòu)造器私有private Singleton2() {}//方法同步,調(diào)用效率低public static Singleton2 getInstance() {if (instance == null)instance = new Singleton2();return instance;}public static void main(String[] args) {Singleton2 singleton1 = Singleton2.getInstance();Singleton2 singleton2 = singleton1.getInstance();System.out.println(singleton1 == singleton2);//true}
}
3.?雙重檢測鎖式:(不建議使用!!)
看看代碼就ok~
package 設計模式;/*** 測試雙重檢查鎖單例模式* 不建議使用*/
public class Singleton3 {//類初始化時,不初始化這個對象(延時加載,真正用的時候才加載)public static Singleton3 instance = null;//構(gòu)造器私有private Singleton3() {}//方法同步,調(diào)用效率低public static Singleton3 getInstance() {if (instance == null) {Singleton3 sc;synchronized (Singleton3.class) {sc = instance;if (sc == null) {synchronized (Singleton3.class) {if (sc == null)sc = new Singleton3();}instance = sc;}}}return instance;}public static void main(String[] args) {Singleton3 singleton1 = Singleton3.getInstance();Singleton3 singleton2 = singleton1.getInstance();System.out.println(singleton1 == singleton2);//true}
}
4.?靜態(tài)內(nèi)部類式:
要點:
- 外部類沒有 static屬性,則不會像餓漢式那樣立即加載對象
- 只有真正調(diào)用 getinstance(),才會加載靜態(tài)內(nèi)部類。加載類時是線程安全的。 Instance是 static final類型,保證了內(nèi)存中只有這樣一個實例存在,而且只能被賦值一次,從而保證了線程安全性。
- 兼?zhèn)淞瞬l(fā)高效調(diào)用和延遲加載的優(yōu)勢!
package 設計模式;/*** 測試靜態(tài)內(nèi)部類單例模式* 線程安全,調(diào)用效率高,并且實現(xiàn)延遲加載*/
public class Singleton4 {//靜態(tài)內(nèi)部類private static class SingletonClassInstance {private static final Singleton4 instance = new Singleton4();}//構(gòu)造器私有private Singleton4() {}public static Singleton4 getInstance() {return SingletonClassInstance.instance;}public static void main(String[] args) {Singleton4 singleton1 = Singleton4.getInstance();Singleton4 singleton2 = singleton1.getInstance();System.out.println(singleton1 == singleton2);//true}
}
5.?枚舉單例:
優(yōu)點: 實現(xiàn)簡單,枚舉本身就是單例模式。由JVM從根本上提供保障!避免通過反射和反序列化的漏洞!
缺點:無延遲加載
package 設計模式;/*** 測試枚舉單例模式* 實現(xiàn)簡單,枚舉本身就是單例模式。由JVM從根本上提供保障!避免通過反射和反序列化的漏洞!* 無延時加載*/
public enum Singleton5 {//這個枚舉元素,本身就是單例模式INSTANCE;//添加自己需要的操作public void singletonOperation() {}public static void main(String[] args) {Singleton5 singleton1 = Singleton5.INSTANCE;Singleton5 singleton2 = singleton1.INSTANCE;System.out.println(singleton1 == singleton2);//true}
}
三:反射和反序列化漏洞及其解決方案
1.反射可以破解上面幾種實現(xiàn)方式~(不包含枚舉式)
示例:通過反射破解靜態(tài)內(nèi)部類的單例模式(聯(lián)系上方靜態(tài)內(nèi)部類實現(xiàn)單例模式的代碼)
package 設計模式;import java.lang.reflect.Constructor;/*** 避免通過反射破解靜態(tài)內(nèi)部類單例模式*/
public class singleProblem1 {public static void main(String[] args) throws Exception {Singleton4 s1 = Singleton4.getInstance();Singleton4 s2 = Singleton4.getInstance();System.out.println(s1);System.out.println(s2);//通過反射直接調(diào)用私有構(gòu)造器Class c = Class.forName("設計模式.Singleton4");System.out.println(c);Constructor constructor = c.getDeclaredConstructor(null);constructor.setAccessible(true);Singleton4 s3 = (Singleton4) constructor.newInstance();Singleton4 s4 = (Singleton4) constructor.newInstance();System.out.println(s3);System.out.println(s4);}
}
根據(jù)結(jié)果,發(fā)現(xiàn)產(chǎn)生了兩個新對象,我們的解決方案就是?在私有構(gòu)造器中手動拋出異常控制,在私有構(gòu)造器內(nèi)加入以下代碼
解決方法:可以在私有構(gòu)造器中手動拋出異常控制
//構(gòu)造器私有private Singleton4() {if (getInstance() != null)throw new RuntimeException();}
?根據(jù)實驗結(jié)果發(fā)現(xiàn),已經(jīng)無法通過反射用私有構(gòu)造器創(chuàng)建新的對象!!
解決成功!!!
?
2.反序列化可以破解上面幾種(不包含枚舉式)實現(xiàn)方式!
示例:通過反序列化破解靜態(tài)內(nèi)部類的單例模式(聯(lián)系上方靜態(tài)內(nèi)部類實現(xiàn)單例模式的代碼)
package 設計模式;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;/*** 避免通過反射破解靜態(tài)內(nèi)部類單例模式*/
public class singleProblem1 {public static void main(String[] args) throws Exception {Singleton4 s1 = Singleton4.getInstance();Singleton4 s2 = Singleton4.getInstance();System.out.println(s1);System.out.println(s2);//通過反序列化的的方式構(gòu)造多個對象//序列化ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D:/學習/IDEA project/project/src/設計模式/a.txt"));out.writeObject(s1);out.close();//反序列化ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:/學習/IDEA project/project/src/設計模式/a.txt"));Singleton4 s3 = (Singleton4) in.readObject();System.out.println(s3);//如果沒有readResolve方法則會構(gòu)造出一個新對象,破解了單例}
}
根據(jù)結(jié)果,發(fā)現(xiàn)產(chǎn)生了一個新對象,我們的解決方案就是在上方靜態(tài)內(nèi)部類實現(xiàn)單例的代碼中加入readResolve函數(shù)
? 解決方法:可以通過定義readResolve()防止獲得不同對象。
??反序列化時,如果對象所在類定義了readResolve(),(實際是一種回調(diào)),定義返回哪個對象
//為了防止反序列化破解,添加此方法//如果定義了readResolve方法,則直接返回此方法指定的對象,而不需要再單獨創(chuàng)建對象private Object readResolve() {return getInstance();}
根據(jù)新的實驗結(jié)果,我們發(fā)現(xiàn)新創(chuàng)建出來的對象與通過單例模式創(chuàng)建出的對象相同,即解決成功!!
四:測試單例模式的效率?
通過一下代碼可以測試上述五種方法效率,一下為測試Singleton1,即餓漢式實現(xiàn)單例的時間!!!!
package 設計模式;import java.util.concurrent.CountDownLatch;public class testtime {public static void main(String[] args) throws InterruptedException {long start = System.currentTimeMillis();int threadNum = 10;CountDownLatch countDownLatch = new CountDownLatch(threadNum);for (int i = 0; i < 10; i++) {new Thread(new Runnable() {@Overridepublic void run() {for (int j = 0; j < 100000; j++)Singleton1.getInstance();countDownLatch.countDown();}}).start();}countDownLatch.await();//main線程阻塞,知道計數(shù)器變?yōu)?,才會繼續(xù)往下執(zhí)行l(wèi)ong end = System.currentTimeMillis();System.out.println("總耗時:" + (end - start));}
}
其中,很重要的一個類
CountDownLatch
同步輔助類,在完成組正在其他線程中執(zhí)行的操作之前,它允許一個或多個線程一直等待
| countDown() | 當前線程調(diào)此方法,則計數(shù)減1(建議放在 finally里執(zhí)行) |
| await() | 調(diào)用此方法會一直阻塞當前線程,直到計時器的值為0 |
在此方法中,在主線程執(zhí)行后,啟動了十個線程?,其中通過CountDownLatch類 實例化出來的countdownlatch相當于一個計數(shù)器,初始化為10,每當一個線程執(zhí)行完后減一,一直阻塞著主線程,直到這十個線程都執(zhí)行完畢,則主線程停止阻塞,記錄最后的時間end,則end-start即為執(zhí)行這是個線程所需要的時間!
THE END
總結(jié)
以上是生活随笔為你收集整理的GOF23设计模式(创建型模式)单例模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Qt实现 指针式时钟+动态时钟 (详细注
- 下一篇: GOF23设计模式(创建型模式)工厂模式