上一篇 <<<觀察者模式(Observer Pattern)
下一篇 >>>創(chuàng)建對象的方式匯總
單例模式:一個類僅有一個實例译秦,并提供一個訪問它的全局訪問點。
- 優(yōu)點:減少代碼冗余、提高代碼復(fù)用性荠耽、安全性追逮、隱藏真實角色逊桦、非入侵泣栈、節(jié)約內(nèi)存絮姆、重復(fù)利用
-
缺點:線程安全問題,數(shù)量很多的話容易導(dǎo)致內(nèi)存泄露
應(yīng)用場景
- spring IOC容器
- 線程池(數(shù)據(jù)庫秩霍、多線程)
- 枚舉、常量類
- 配置文件常量
- 日志
- HttpApplication蚁阳、servlet
- windows系統(tǒng)的任務(wù)管理器铃绒、回收站、網(wǎng)站計數(shù)器螺捐、顯卡驅(qū)動颠悬、打印機等
單例優(yōu)缺點
優(yōu)點:
a、防止其他對象自身的實例化定血,保證所有的對象都訪問一個實例
b赔癌、類在實例化進(jìn)程上有一定的伸縮性
c、提供了對唯一實例的受控訪問
d澜沟、節(jié)約系統(tǒng)創(chuàng)建和銷毀對象的資源
e灾票、允許對共享資源的多重占用
缺點:
a、不適用于變化的對象
b茫虽、沒有抽象層刊苍,所以擴展難度很大
c、職責(zé)過重濒析,違背了單一職責(zé)原則
d正什、濫用容易導(dǎo)出溢出或丟失情況
單例模式的種類
第一個if是判斷實例對象是否存在,針對讀寫都有的操作号杏。
第二個if是對同時進(jìn)行初始化操作的多個線程進(jìn)入鎖狀態(tài)的再次判斷婴氮,如果前面已經(jīng)有創(chuàng)建過的話,將不再實例化,徹底解決單例問題主经。
靜態(tài)內(nèi)部類方式與雙重檢驗鎖的區(qū)別
雙重檢驗鎖是采用了懶漢式并且只針對寫操作加了鎖荣暮,保證了線程的安全又加快了讀的操作。但如果多線程進(jìn)來的時候旨怠,寫操作會存在阻塞的現(xiàn)象渠驼,效率不高。
靜態(tài)內(nèi)部類是在使用時才會被初始化鉴腻,但內(nèi)部類又使用了餓漢式的模式迷扇,所以既擁有餓漢式的線程安全,又支持懶漢式的資源不浪費爽哎,不存在線程阻塞的情況蜓席,比雙重檢驗鎖更加的高效。
單例模式創(chuàng)建方式如何選擇
- 如果不需要延遲加載單例课锌,可以使用枚舉或者餓漢式厨内,相對來說枚舉性好于餓漢式。
- 如果需要延遲加載渺贤,可以使用靜態(tài)內(nèi)部類或者懶漢式雏胃,相對來說靜態(tài)內(nèi)部類好于懶漢式。
單例模式如何破壞
a志鞍、利用反射機制瞭亮,通過declaredConstructors.setAccessible(true);方法即可破解構(gòu)造函數(shù)私有化的缺陷
Class<?> classInfo = Class.forName("com.jgspx.singleton.crack.regex.RegixObject");
Constructor declaredConstructors = classInfo.getDeclaredConstructor();
declaredConstructors.setAccessible(true);
Object o = declaredConstructors.newInstance();
- 破解:在構(gòu)造函數(shù)里判斷如果已經(jīng)實例化的話就拋出異常,防止多次被實例化
public class RegixObject {
/**
* 如果是餓漢式固棚,則反射機制調(diào)用構(gòu)造函數(shù)的時候就會報錯
*/
private static RegixObject regixObject = new RegixObject();
public static RegixObject getInstance(){
/**
* 如果是懶漢式统翩,先利用反射,然后代碼調(diào)用此洲,則構(gòu)造函數(shù)里加上判斷也沒用
*/
if(regixObject==null){
regixObject = new RegixObject();
}
return regixObject;
}
private RegixObject(){
//加上這一句話厂汗,可破解多次初始化的情況
if(regixObject!=null){
throw new RuntimeException("初始化已執(zhí)行過");
}
}
}
b、利用序列化呜师,將序列化后的結(jié)果存入硬盤娶桦,然后再次反序列化,等到的結(jié)果就和原先的不一致
序列化:將存放在內(nèi)存中的對象信息序列操作后變成可以存放在硬盤的數(shù)據(jù)汁汗。
反序列化:將硬盤上的數(shù)據(jù)解析后放入到內(nèi)存中趟紊。
public static void main(String[] args) throws Exception{
User instance = User.getInstance();
FileOutputStream fos = new FileOutputStream("./user.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("./user.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
User singleton2 = (User) ois.readObject();
System.out.println(singleton2==instance);
}
- 破解:在原有類中增加方法readResolve()
public class User implements Serializable {
public static User user = new User();
public static User getInstance(){
return user;
}
//返回序列化獲取對象 ,保證為單例
public Object readResolve() {
return user;
}
}
- ObjectInputStream
- case TC_OBJECT:return checkResolve(readOrdinaryObject(unshared)); *
- ObjectStreamClass desc = readClassDesc(false);---獲取當(dāng)前類的超類(沒有實現(xiàn)Serializable的)碰酝。eg:User extend A霎匈,且A沒有實現(xiàn)Serializable,則desc=A.class送爸,否則desc=Object.class
- if (desc.hasReadResolveMethod()){Object rep = desc.invokeReadResolve(obj);}
最強單例模式--枚舉
a. 枚舉單例源碼
public enum SingleV6 {
TT;
SingleV6(){
System.out.println("我是無參構(gòu)造函數(shù)被執(zhí)行到了");
}
public void add(){
System.out.println("添加操作被啟動");
}
}
b. 枚舉單例反編譯后的代碼
public final class SingleV6 extends Enum
{
public static SingleV6[] values()
{
return (SingleV6[])$VALUES.clone();
}
public static SingleV6 valueOf(String name)
{
return (SingleV6)Enum.valueOf(com/jarye/singleton/v6/SingleV6, name);
}
private SingleV6(String s, int i)
{
super(s, i);
System.out.println("\u6211\u662F\u65E0\u53C2\u6784\u9020\u51FD\u6570\u88AB\u6267\u884C\u5230\u4E86");
}
public void add()
{
System.out.println("\u6DFB\u52A0\u64CD\u4F5C\u88AB\u542F\u52A8");
}
public static final SingleV6 TT;
private static final SingleV6 $VALUES[];
static
{
TT = new SingleV6("TT", 0);
$VALUES = (new SingleV6[] {
TT
});
}
}
枚舉類型其實就是class類铛嘱,繼承于Enum類暖释,內(nèi)置了name、ordinal和values方法墨吓,且沒有默認(rèn)的無參構(gòu)造函數(shù)球匕。
A、底層轉(zhuǎn)換類繼承Enum
B帖烘、使用靜態(tài)代碼快方式亮曹,當(dāng)靜態(tài)代碼快執(zhí)行的時候初始化該對象
c. 枚舉單例無法被破解的原因
- 無參方式破解
Class<?> classInfo = Class.forName("com.jarye.singleton.v6.SingleV6");
Constructor<?> declaredConstructor = classInfo.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
declaredConstructor.newInstance();
- 參照源碼的有參方式破解
Class<?> classInfo2 = Class.forName("com.jarye.singleton.v6.SingleV6");
Constructor<?> declaredConstructor2 = classInfo2.getDeclaredConstructor(String.class,Integer.class);
declaredConstructor2.setAccessible(true);
declaredConstructor2.newInstance("zhangsan",3);
d. 序列化破解失敗原因
Singleton06 instance = Singleton06.INSTANCE;
FileOutputStream fos = new FileOutputStream("./a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Singleton06 singleton3 = Singleton06.INSTANCE;
oos.writeObject(singleton3);
oos.close();
fos.close();
System.out.println("----------從硬盤中反序列化對象到內(nèi)存中------------");
//2.從硬盤中反序列化對象到內(nèi)存中
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./a.txt"));
/**
* 方法點進(jìn)去后可查看:
* case TC_ENUM:
* return checkResolve(readEnum(unshared));
* readEnum走的是:Enum<?> en = Enum.valueOf((Class)cl, name);
* 最后是從map中取出的值
* Map<String, T> enumConstantDirectory
*
*/
// 從新獲取一個新的對象
Singleton06 singleton4 = (Singleton06) ois.readObject();
System.out.println(instance == singleton4);
相關(guān)文章鏈接:
<<<23種常用設(shè)計模式總覽
<<<代理模式(Proxy Pattern)
<<<裝飾模式(Decorator Pattern)
<<<觀察者模式(Observer Pattern)
<<<責(zé)任鏈模式(Chain of Responsibility Pattern)
<<<策略模式(Strategy Pattern)
<<<模板方法模式(Template Pattern)
<<<外觀/門面模式(Facade Pattern)
<<<建造者模式(Builder Pattern)
<<<適配器模式(Adapter Pattern)
<<<原型模式(Prototype Pattern)
<<<工廠相關(guān)模式(Factory Pattern)