YSLaunchar-a1.0 模型 基本介紹 本文不考慮所有具體的實現方法,之後會有更完整第二版發出 該程式計劃使用 julia 語言編寫,目前版本(1.7)並不包含類(class),取而代之,我會使用 julia 提供的兩種結構體完成。 考慮了很久,我將會把所有版本,玩家列表使用字典的形式。 主 ...
定義:單例模式屬於創建型模式,該類負責創建自己的對象實例,並且確保只有單個對象被創建,同時該類提供了一種全局訪問其唯一實例對象的方式;這個定義中有三個要點:1、單例類只能有一個實例;2、單例類必須自己創建自己的唯一實例;3、單例類必須可以給其他所有對象提供這一唯一實例;
意圖:保證一個類僅有一個實例,並提供一個訪問它的全局節點;
主要解決:一個全局使用的對象的頻繁地創建和銷毀;
何時使用:想控制類的數目,節省系統資源的時候;
如何解決:判斷系統是否已經有這個實例,有則直接返回,沒有則創建返回;
關鍵代碼:構造函數私有;
應用實例:
1、一個班級只有一個班主任;
2、Windows是多進程多線程的操作系統,在操作一個文件的時候,就不可避免地出現多個線程或者多個進程同時操作一個文件的現象,所有的文件處理必須通過一個唯一的實例來進行處理;
優點:
1、記憶體中只有相關對象的一個實例,減少了記憶體開銷,尤其是頻繁地創建和銷毀的實例;
2、避免了對資源的多重占用;
缺點:
1、沒有介面,不能繼承,與單一職責衝突,一個類應該只關心內部邏輯,而不應該關係外部怎麼來實例化它;
使用場景:
1、要求生產唯一序列號;
2、WEB中的計數器,不用每次刷新都在資料庫中更新一次,用單例先緩存起來;
3、創建一個對象需要消耗過多的系統資源的時候,比如I/O和資料庫連接等等;
單例模式的幾種常見寫法:
package cn.com.pep.model.singleton.single2; /** * * @Title: Singleton * @Description: 線程安全的懶漢式 * @author wwh * @date 2022-8-24 15:07:07 */ public class Singleton { /** * 是否是懶載入:是 * 是否線程安全:是 * 實現難度:容易 * 描述:這種方式具備很好的lazy loading,能夠在多線程中很好的工作,但是效率比較低,99%情況下不需要同步; * 優點:第一次調用才初始化,避免了記憶體的浪費; * 缺點:必須加鎖synchronized才能保證單例,在靜態方法上加的是類鎖會影響效率; */ private static Singleton instance; private Singleton() { // TODO Auto-generated constructor stub } public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
package cn.com.pep.model.singleton.single3; /** * * @Title: Singleton * @Description:餓漢式單例 * @author wwh * @date 2022-8-24 15:45:01 */ public class Singleton { /** * 是否是懶載入:否 * 是否線程安全:是 * 實現難度:容易 * 描述:比較常用,但是容易產生垃圾對象; * 優點:沒有加鎖,執行效率會提高; * 缺點:類載入時候就會初始化,浪費記憶體; * 它基於classloader機制避免了多線程同步的問題,不過,instance在類裝載的時候就實例化,雖然導致類裝載的原因有很多種,在單例模式中大多數都是調用getInstance, * 但也不確定有其他方式導致類載入,這時候就沒有達到懶載入的效果; */ private static Singleton instance = new Singleton(); private Singleton() { // TODO Auto-generated constructor stub } public static Singleton getInstance() { return instance; } package cn.com.pep.model.singleton.single4;
/** * * @Title: Singleton * @Description:雙重校驗鎖 * @author wwh * @date 2022-8-24 16:02:19 */ public class Singleton { /** * 是否是懶載入:是 * 是否多線程安全:是 * 實現難度:較複雜 * 描述:這種方式採用鎖雙鎖機制,安全且在多線程情況下能保持高性能; */ // volatile關鍵字在這使用時,是利用其禁止虛擬機的指令重排特點,防止返回一個未被完整初始化的對象; private volatile static Singleton instance; private Singleton() { // TODO Auto-generated constructor stub } public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) {
//假如當前有兩個線程同時執行獲取實例的方法,cpu執行A線程執行到這個地方說明instance==null這個條件成立,此時發生了線程切換,當前線程釋放了類鎖,此時
//cpu執行B線程,執行完之後instance!=null,然後又切換回A線程執行,如果不instance== null做判斷會再將instace初始化一次,可能會引起其他未知的錯誤。
if (instance == null) { instance = new Singleton(); } } } return instance; } }
package cn.com.pep.model.singleton.single5; /** * @Title: Singleton * @Description: 靜態內部類 * @author wwh * @date 2022-8-24 16:12:51 */ public class Singleton { /** * 是否是懶載入:是 * 是否是多線程安全:是 * 實現難度:一般 * 描述:這種方式能達到和雙檢鎖方式一樣的功效,實現更簡單; * 這種方式同樣利用了classloader機制來保證實例初始化的時候只有一個線程,它跟餓漢式不同的是: * 第三種方式,只要Sington類被載入了,那麼instance實例就會被初始化,沒有達到懶載入的效果, * 而這種方式即使Singleton類被載入了,但是SingletonHolder類沒有被主動使用,只有通過顯示調用 * getInstance()方法時候,才會顯示載入SingletonHolder類,從而實例化instance對象; */ private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton() { // TODO Auto-generated constructor stub } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } }
以上的四種寫法大部分精力都在致力於保證多線程併發下的線程安全;因為構造方法都是私有的,也可以避免由反射獲取對象實例(在未開啟忽略許可權檢查的情況下);但是還沒有解決由於序列化和反序列化導致獲取的不是同一個實例的問題,那麼怎麼解決呢?
答案是:在單例類中增加一個readResolve()方法,返回這個唯一的實例,就可以解決這個問題啦,這是因為反序列化的readObject()底層會做個判斷,假如當前反序列化的目標對象有ReadResolve()方法,那麼會調用目標類的這個方法返回一個實例對象。
最後一種單例寫法,通過枚舉來實現單例,這種比較推薦,這種寫法天然就是線程安全的,所以我們就不需要花費大量的精力來保證線程安全,同時既可以防止反序列化生成不同實例,又可以防止反射生成不同實例:
package cn.com.pep.model.singleton.single6; import java.io.Serializable; /** * * @Title: Singleton * @Description: * @author wwh * @date 2022-8-24 16:49:19 */ public enum Singleton implements Serializable{ SPRING{ @Override public void say(String message) { System.err.println(message); } }; public abstract void say(String message); }
為什麼既可以避免反射生成不同實例,又可以避免反序列化生成不同實例呢?下麵我們一一道來:
1、至於為什麼通過反射不能生成實例對象呢?請看下這個枚舉類反編譯之後的代碼,同時還包含一個匿名內部類:
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. package cn.com.pep.model.singleton.single6; import java.io.PrintStream; import java.io.Serializable; public abstract class Singleton extends Enum implements Serializable { public static final Singleton SPRING; private static final Singleton ENUM$VALUES[]; private Singleton(String s, int i) { super(s, i); } public abstract void say(String s); public static Singleton[] values() { Singleton asingleton[]; int i; Singleton asingleton1[]; System.arraycopy(asingleton = ENUM$VALUES, 0, asingleton1 = new Singleton[i = asingleton.length], 0, i); return asingleton1; } public static Singleton valueOf(String s) { return (Singleton) Enum.valueOf(cn / com / pep / model / singleton / single6 / Singleton, s); } Singleton(String s, int i, Singleton singleton) { this(s, i); } static { SPRING = new Singleton("SPRING", 0) { public void say(String message) { System.err.println(message); } }; ENUM$VALUES = (new Singleton[]{SPRING}); } }
/* */ // Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. /* */ package cn.com.pep.model.singleton.single6; /* */ /* */ import java.io.PrintStream; /* */ /* */ /* */ /* */ /* */ class Singleton$1 extends Singleton /* */ { /* */ /* */ Singleton$1(String s, int i) /* */ { /* 14 */ super(s, i, null); /* */ } /* */ public void say(String message) /* */ { /* 18 */ System.err.println(message); /* */ } /* */ }
仔細分析這個枚舉反編譯之後的代碼,枚舉類的enum其實是個關鍵字,普通的枚舉類其實都是一個繼承了Enum的普通的java子類,所有的枚舉類都具有這個公共的父類,列子中的枚舉類也包含了兩個靜態屬性SPRING和ENUM$VALUES,這兩個靜態屬性的初始化是在靜態塊中進行的,類載入執行靜態塊中的代碼時候會初始化給這兩個靜態屬性賦值,而類載入的過程會調用ClassLoader類的loadClass()方法,這個方法的方法體是用synchronized修飾的,必定是線程安全的,所以我們說這種寫法是線程安全的原因在這,並且這兩個靜態屬性還是final修飾的,一旦初始化完成則不允許修改,初始化完成之後我們的SPRING = new Singleton("SPRING", 0),又因為我們這個枚舉類中包含了抽象方法,根據java規範,抽象方法只能存在於介面或者抽象方法中,所以這個子類反編譯之後必然是用abstract修飾的抽象類,眾所周知抽象類是不能被實例化的,並且這兩個類中沒有定義無參構造方法,所以不能被反射實例化了。
2、為什麼能防止反序列化生成多個對象呢(當然我們說序列化的前提是類都實現了序列化介面)?
首先要從枚舉類的序列化說起,枚舉類在序列化的時候其實只是將Singleton(key,value)中的key進行了序列化,而反序列化的時候也是通過這個key去對應的map中獲取對應的Singleton(key,value)實例的,大致就是這麼個邏輯,有興趣可以翻翻源碼看看。
本文來自博客園,作者:一隻烤鴨朝北走,僅用於技術學習,所有資源都來源於網路,部分是轉發,部分是個人總結。歡迎共同學習和轉載,轉載請在醒目位置標明原文。如有侵權,請留言告知,及時撤除。轉載請註明原文鏈接:https://www.cnblogs.com/wha6239/p/16635112.html