23种设计模式学习记录之单例设计模式
本文所有案例代碼
碼云:https://gitee.com/helloworld6379/designPattern
Github:Github地址
設(shè)計(jì)模式概述
1 設(shè)計(jì)模式是程序員在面對(duì)同類軟件工程設(shè)計(jì)問(wèn)題所總結(jié)出來(lái)的有用的經(jīng)驗(yàn),模式不是代碼,而是某類問(wèn)題的通
用解決方案,設(shè)計(jì)模式(Design pattern)代表了最佳的實(shí)踐。這些解決方案是眾多軟件開(kāi)發(fā)人員經(jīng)過(guò)相當(dāng)長(zhǎng)的
一段時(shí)間的試驗(yàn)和錯(cuò)誤總結(jié)出來(lái)的。
2 設(shè)計(jì)模式的本質(zhì)提高 軟件的維護(hù)性,通用性和擴(kuò)展性,并降低軟件的復(fù)雜度。
3 設(shè)計(jì)模式并不局限于某種語(yǔ)言,java,php,c++ 都有設(shè)計(jì)模式.
設(shè)計(jì)模式類型
設(shè)計(jì)模式分為三種類型,共 23 種
1 創(chuàng)建型模式:單例模式、抽象工廠模式、原型模式、建造者模式、工廠模式。
2 結(jié)構(gòu)型模式:適配器模式、橋接模式、裝飾模式、組合模式、外觀模式、享元模式、代理模式。
3 行為型模式:模版方法模式、命令模式、訪問(wèn)者模式、迭代器模式、觀察者模式、中介者模式、備忘錄模式、解釋器模式(Interpreter 模式)、狀態(tài)模式、策略模式、職責(zé)鏈模式(責(zé)任鏈模式)。
不同的資料上對(duì)分類和名稱略有差別
單例設(shè)計(jì)模式
所謂類的單例設(shè)計(jì)模式,就是采取一定的方法保證在整個(gè)的軟件系統(tǒng)中,對(duì)某個(gè)類只能存在一個(gè)對(duì)象實(shí)例,并且該類只提供一個(gè)取得其對(duì)象實(shí)例的方法(靜態(tài)方法)。
比如 Hibernate 的 SessionFactory,它充當(dāng)數(shù)據(jù)存儲(chǔ)源的代理,并負(fù)責(zé)創(chuàng)建 Session 對(duì)象。SessionFactory 并不是輕量級(jí)的,一般情況下,一個(gè)項(xiàng)目通常只需要一個(gè) SessionFactory 就夠,這是就會(huì)使用到單例模式。
Windows的任務(wù)管理器就是一個(gè)單例模式的應(yīng)用,只能打開(kāi)一個(gè)。
單例模式有八種方式:
1. 餓漢式(靜態(tài)常量)
public class Person {//1. 構(gòu)造器私有化, 外部不能newprivate Person() {}//2.本類內(nèi)部創(chuàng)建對(duì)象實(shí)例private final static Person person = new Person();//3. 提供一個(gè)公有的靜態(tài)方法,返回實(shí)例對(duì)象public static Person getPerson() {return person;} } public class SingletonTest {public static void main(String[] args) {//測(cè)試Person person1 = Person.getPerson();Person person2 = Person.getPerson();System.out.println(person1 == person2); // trueSystem.out.println("person1.hashCode=" + person1.hashCode());System.out.println("person2.hashCode=" + person2.hashCode());} }優(yōu)缺點(diǎn)說(shuō)明:
1 優(yōu)點(diǎn):這種寫(xiě)法比較簡(jiǎn)單,就是在類裝載的時(shí)候就完成實(shí)例化。避免了線程同步問(wèn)題。
2 缺點(diǎn):在類裝載的時(shí)候就完成實(shí)例化,沒(méi)有達(dá)到 Lazy Loading 的效果。如果從始至終從未使用過(guò)這個(gè)實(shí)例,則會(huì)造成內(nèi)存的浪費(fèi)。
3 這種方式基于 classloder 機(jī)制避免了多線程的同步問(wèn)題,不過(guò),person 在類裝載時(shí)就實(shí)例化,在單例模式中大多數(shù)都是調(diào)用 getPerson 方法,但是導(dǎo)致類裝載的原因有很多種,因此不能確定有其他的方式(或者其他的靜態(tài)方法)導(dǎo)致類裝載,這時(shí)候初始化 person 就沒(méi)有達(dá)到 lazy loading 的效果。
4 結(jié)論:這種單例模式可用,可能造成內(nèi)存浪費(fèi)。
2 餓漢式(靜態(tài)代碼塊)
/*** @Description 餓漢式(靜態(tài)代碼塊)* @Author LiuXing* @Date 2020/05/21 23:04*/ public class Person {//1.本類內(nèi)部創(chuàng)建對(duì)象實(shí)例private static Person person;//2. 構(gòu)造器私有化, 外部不能newprivate Person() {}static { // 在靜態(tài)代碼塊中,創(chuàng)建單例對(duì)象person = new Person();}//3. 提供一個(gè)公有的靜態(tài)方法,返回實(shí)例對(duì)象public static Person getPerson() {return person;} }優(yōu)缺點(diǎn)說(shuō)明:
1 這種方式和上面的方式其實(shí)類似,只不過(guò)將類實(shí)例化的過(guò)程放在了靜態(tài)代碼塊中,也是在類裝載的時(shí)候,就執(zhí)行靜態(tài)代碼塊中的代碼,初始化類的實(shí)例。優(yōu)缺點(diǎn)和上面是一樣的。
2 結(jié)論:這種單例模式可用,但是可能造成內(nèi)存浪費(fèi)
3 懶漢式(線程不安全)
/*** @Description 懶漢式(線程不安全)* @Author LiuXing* @Date 2020/05/21 23:04*/ public class Person {//1.本類內(nèi)部創(chuàng)建對(duì)象實(shí)例private static Person person;//2. 構(gòu)造器私有化, 外部不能newprivate Person() {}//3. 提供一個(gè)公有的靜態(tài)方法,當(dāng)使用到該方法時(shí),才去創(chuàng)建public static Person getPerson() {if(person == null ){person = new Person();}return person;} }優(yōu)缺點(diǎn)說(shuō)明:
1 起到了 Lazy Loading 的效果,但是只能在單線程下使用。
2 如果在多線程下,一個(gè)線程進(jìn)入了 if (person == null)判斷語(yǔ)句塊,還未來(lái)得及往下執(zhí)行,另一個(gè)線程也通過(guò)了這個(gè)判斷語(yǔ)句,這時(shí)便會(huì)產(chǎn)生多個(gè)實(shí)例。所以在多線程環(huán)境下不可使用這種方式
3) 結(jié)論:在實(shí)際開(kāi)發(fā)中,不要使用這種方式.
4 懶漢式(線程安全,同步方法)
/*** @Description 懶漢式(線程安全,同步方法)* @Author LiuXing* @Date 2020/05/21 23:04*/ public class Person {//1.本類內(nèi)部創(chuàng)建對(duì)象實(shí)例private static Person person;//2. 構(gòu)造器私有化, 外部不能newprivate Person() {}// 3. 提供一個(gè)公有的靜態(tài)方法,當(dāng)使用到該方法時(shí),才去創(chuàng)建// 加入同步處理的代碼,解決線程安全問(wèn)題public static synchronized Person getPerson() {if(person == null ){person = new Person();}return person;} }優(yōu)缺點(diǎn)說(shuō)明:
1 解決了線程安全問(wèn)題
2 效率太低了,每個(gè)線程在想獲得類的實(shí)例時(shí)候,執(zhí)行 getPerson()方法都要進(jìn)行同步。而其實(shí)這個(gè)方法只執(zhí)行一次實(shí)例化代碼就夠了,后面的想獲得該類實(shí)例,直接 return 就行了。方法進(jìn)行同步效率太低
3 結(jié)論:在實(shí)際開(kāi)發(fā)中,不推薦使用這種方式
5 懶漢式(線程安全,同步代碼塊)
/*** @Description 懶漢式(線程安全,同步代碼塊)* @Author LiuXing* @Date 2020/05/21 23:04*/ public class Person {//1.本類內(nèi)部創(chuàng)建對(duì)象實(shí)例private static Person person;//2. 構(gòu)造器私有化, 外部不能newprivate Person() {}// 3. 提供一個(gè)公有的靜態(tài)方法,當(dāng)使用到該方法時(shí),才去創(chuàng)建// 加入同步處理的代碼,解決線程安全問(wèn)題public static Person getPerson() {if(person == null ){synchronized (Person.class){person = new Person();}}return person;} }不推薦使用
6 雙重檢查
package com.fighting.pattern.singleton.type6;/*** @Description 雙重檢查* @Author LiuXing* @Date 2020/05/21 23:04*/ public class Person {//1.本類內(nèi)部創(chuàng)建對(duì)象實(shí)例private volatile static Person person;//2. 構(gòu)造器私有化, 外部不能newprivate Person() {}// 3. 寫(xiě)一個(gè)靜態(tài)的共有方法,加入雙重檢查代碼,解決線程安全問(wèn)題,同時(shí)解決懶加載問(wèn)題public static Person getPerson() {if (person == null){synchronized (Person.class){if (person ==null){person = new Person();}}}return person;} }優(yōu)缺點(diǎn)說(shuō)明:
1 Double-Check 概念是多線程開(kāi)發(fā)中常使用到的,如代碼中所示,我們進(jìn)行了兩次 if (person == null)檢查,這樣就可以保證線程安全了。
2 這樣,實(shí)例化代碼只用執(zhí)行一次,后面再次訪問(wèn)時(shí),判斷 if (person == null),直接 return 實(shí)例化對(duì)象,也避免的反復(fù)進(jìn)行方法同步.
3 線程安全;延遲加載;效率較高
4 結(jié)論:在實(shí)際開(kāi)發(fā)中,推薦使用這種單例設(shè)計(jì)模式
7 靜態(tài)內(nèi)部類
package com.fighting.pattern.singleton.type7;/*** @Description 靜態(tài)內(nèi)部類* @Author LiuXing* @Date 2020/05/21 23:04*/ public class Person {//1.本類內(nèi)部創(chuàng)建對(duì)象實(shí)例private static Person person;//2. 構(gòu)造器私有化, 外部不能newprivate Person() {}// 3. 寫(xiě)一個(gè)靜態(tài)內(nèi)部類,該類中有一個(gè)靜態(tài)屬性 PERSONprivate static class SingletonInstance{private static final Person PERSON = new Person();}//4 提供一個(gè)靜態(tài)的公有方法,直接返回 SingletonInstance.INSTANCEpublic static synchronized Person getPerson() {return SingletonInstance.PERSON;} }優(yōu)缺點(diǎn)說(shuō)明:
1 這種方式采用了類裝載的機(jī)制來(lái)保證初始化實(shí)例時(shí)只有一個(gè)線程。
2 靜態(tài)內(nèi)部類方式在 Person 類被裝載時(shí)并不會(huì)立即實(shí)例化,而是在需要實(shí)例化時(shí),調(diào)用 getPerson 方法,才會(huì)裝載 Person 類,從而完成 Person 的實(shí)例化。
3 類的靜態(tài)屬性只會(huì)在第一次加載類的時(shí)候初始化,所以在這里,JVM 幫助我們保證了線程的安全性,在類進(jìn)行
初始化時(shí),別的線程是無(wú)法進(jìn)入的。
4 優(yōu)點(diǎn):避免了線程不安全,利用靜態(tài)內(nèi)部類特點(diǎn)實(shí)現(xiàn)延遲加載,效率高。
5 結(jié)論:推薦使用。
8 枚舉
package com.fighting.pattern.singleton.type8;public enum Person {INSTANCE;//屬性public void sayOK(){System.out.println("OK~");} } /*** @Description 枚舉* @Author LiuXing* @Date 2020/05/21 23:03*/ public class SingletonTest {public static void main(String[] args) {System.out.println("枚舉~");Person person1 = Person.INSTANCE;Person person2 = Person.INSTANCE;System.out.println(person1 == person2); // trueSystem.out.println("person1.hashCode=" + person1.hashCode());System.out.println("person2.hashCode=" + person2.hashCode());} }優(yōu)缺點(diǎn)說(shuō)明:
1 這借助 JDK1.5 中添加的枚舉來(lái)實(shí)現(xiàn)單例模式。不僅能避免多線程同步問(wèn)題,而且還能防止反序列化重新創(chuàng)建新的對(duì)象。
2 這種方式是 Effective Java 作者 Josh Bloch 提倡的方式
3 結(jié)論:推薦使用
前七種方式可通過(guò)反射和反序列化的方式破解:
/*** @Description 靜態(tài)內(nèi)部類* @Author LiuXing* @Date 2020/05/21 23:03*/ public class SingletonTest {public static void main(String[] args) throws Exception {System.out.println("靜態(tài)內(nèi)部類~");//測(cè)試Person person1 = Person.getPerson();Person person2 = Person.getPerson();System.out.println(person1 == person2); // trueSystem.out.println("person1.hashCode=" + person1.hashCode());System.out.println("person2.hashCode=" + person2.hashCode());Class<Person> clazz = (Class<Person>)Class.forName("com.fighting.pattern.singleton.type7.Person");Constructor<Person> con = clazz.getDeclaredConstructor(null);con.setAccessible(true);Person person11 = con.newInstance();Person person22 = con.newInstance();System.out.println("通過(guò)反射破解:");System.out.println(person11 == person22); // falseSystem.out.println("person11.hashCode=" + person11.hashCode());System.out.println("person22.hashCode=" + person22.hashCode());System.out.println("person11=" + person11);System.out.println("person22=" + person22);//此種方法測(cè)試時(shí) 實(shí)體類Person實(shí)現(xiàn)Serializable接口FileOutputStream fos = new FileOutputStream( "D:/person.txt" );ObjectOutputStream oos = new ObjectOutputStream( fos );oos.writeObject( person1 );oos.close();fos.close();System.out.println("通過(guò)序列化反序列化破解:");ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/person.txt"));Person person3 = (Person)ois.readObject();System.out.println(person1 == person3); // trueSystem.out.println("person1.hashCode=" + person1.hashCode());System.out.println("person3.hashCode=" + person3.hashCode());System.out.println("person1=" + person1);System.out.println("person3=" + person3);} } 靜態(tài)內(nèi)部類~ true person1.hashCode=460141958 person2.hashCode=460141958 通過(guò)反射破解: false person1.hashCode=1163157884 person2.hashCode=1956725890 通過(guò)序列化反序列化破解: false person1.hashCode=460141958 person2.hashCode=931919113單例模式在 JDK 應(yīng)用的源碼分析
在JDK 中,java.lang.Runtime 就是經(jīng)典的單例模式(餓漢式)
總結(jié)
以上是生活随笔為你收集整理的23种设计模式学习记录之单例设计模式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 初识云计算————虚拟化背景
- 下一篇: LTE: 小区特定参考信号功率与RRU发