上一篇文章從NIO原理到性能優(yōu)化中获列,我 們引入了性能調(diào)優(yōu)的相關(guān)話題,著重了談了一下互聯(lián)網(wǎng)架構(gòu)的演變之路蛔垢。這篇文章击孩,我們將會(huì)開(kāi)啟一個(gè)老生常談的新專(zhuān)題-設(shè)計(jì)模式。
提劍跨騎揮鬼雨鹏漆,白骨如山鳥(niǎo)驚飛巩梢。塵世如潮人如水,只嘆江湖幾人回艺玲!
如果將代碼比作江湖括蝠,那么語(yǔ)法就是一招一式,而設(shè)計(jì)模式則是神秘莫測(cè)的內(nèi)功心法饭聚,將其融會(huì)貫通之日忌警,便是立于不敗之時(shí)。本文分為如下部分:
- 前言
- 設(shè)計(jì)模式分類(lèi)
- 單例
3.1 使用場(chǎng)景
3.2 單例的5種實(shí)現(xiàn)
3.3 餓漢單例
3.3.1 破壞單例
3.3.2 序例化和反序例化的實(shí)現(xiàn)機(jī)制
3.3.3 通過(guò)readResolve 來(lái)改善單例
3.4 懶漢單例
3.4.1 破壞單例
3.5 雙重鎖單例
3.5.1 破壞單例
3.6 內(nèi)部類(lèi)單例
3.6.1 破壞單例
3.7 枚舉單例
3.7.1 破壞單例
3.8 幾種單例的比對(duì)
3.9 基于單例編寫(xiě)文件加載- 總結(jié)
1. 前言
在說(shuō)設(shè)計(jì)模式之前我們先思考幾個(gè)問(wèn)題秒梳。
什么是設(shè)計(jì)模式法绵?
-
什么情況下要用到設(shè)計(jì)模式箕速?
那么帶著這些問(wèn)題,我們推開(kāi)設(shè)計(jì)模式的大門(mén)
2. 設(shè)計(jì)模式分類(lèi)
所謂設(shè)計(jì)模式指的是在某些特定情境下朋譬,針對(duì)某些特定問(wèn)題的某種解決方案盐茎。它不是語(yǔ)法規(guī)定,而是一套解決方案徙赢,使用設(shè)計(jì)模式幫助我們提高代碼的復(fù)用性字柠、可維護(hù)性、健壯性以及安全性
犀忱。同時(shí)更方便和清晰的通過(guò)代碼傳遞我們coding時(shí)的設(shè)計(jì)思路募谎,畢竟好的代碼自己就是注釋。設(shè)計(jì)模式按照意圖可以分為以下幾類(lèi):
意圖分類(lèi) | 解釋 | 模式 |
---|---|---|
創(chuàng)建型 | 創(chuàng)建型模式涉及到對(duì)象的實(shí)例化阴汇,這類(lèi)模式提供一個(gè)方法数冬,將客戶(hù)從對(duì)象的創(chuàng)建種解耦 | 工廠方法、抽象工廠搀庶、單例拐纱、建造者、原型 |
行為型 | 涉及到類(lèi)和對(duì)象如何交互及分配職責(zé) | 模板方法哥倔、命令秸架、迭代器、觀察者咆蒿、策略东抹、狀態(tài)、職責(zé)鏈沃测、中介者缭黔、訪問(wèn)者、備忘者蒂破、解釋器 |
結(jié)構(gòu)型 | 結(jié)構(gòu)型模式可以讓你把類(lèi)或?qū)ο蠼M合到更大的結(jié)構(gòu)中去 | 適配器馏谨、裝飾器、代理附迷、組合惧互、門(mén)面、橋接喇伯、享元 |
值得注意的是:
-
設(shè)計(jì)模式職期間并不是毫無(wú)關(guān)聯(lián)的喊儡,常常一種模式里會(huì)包含另一種模式(如用工工廠方法實(shí)現(xiàn)抽象工廠)
。 -
設(shè)計(jì)模式并不是一塵不變的稻据,常常為了實(shí)現(xiàn)需求而做不同的改變(比如spring的bean是單例管宵,但卻不是我們常用的單例模式,而是注冊(cè)表單例)
攀甚。 -
不要濫用設(shè)計(jì)模式箩朴,采用最簡(jiǎn)單直觀的方式解決問(wèn)題才是最好的選擇,不要過(guò)分設(shè)計(jì)
秋度。
3. 單例
所謂單例即確保一個(gè)類(lèi)只有一個(gè)實(shí)例并提供一個(gè)全局的訪問(wèn)點(diǎn)炸庞。
3.1 使用場(chǎng)景
從單例模式的定義中我們就可以看出單例的使用場(chǎng)景,即只需要一個(gè)對(duì)象的時(shí)候荚斯。那么什么時(shí)候只需要一個(gè)對(duì)象呢埠居?即當(dāng)對(duì)象的創(chuàng)建和銷(xiāo)毀代價(jià)比較高的時(shí)候。如配置文件的加載事期,如spring bean的創(chuàng)建滥壕。
3.2 單例模式的5種實(shí)現(xiàn)方式
單例模式一般有如下實(shí)現(xiàn)方式
- 餓漢
- 懶漢
- 雙重鎖
- 內(nèi)部類(lèi)
- 枚舉
3.3 餓漢單例
所謂餓漢,即類(lèi)初始化的時(shí)候就實(shí)例化一個(gè)不可變的靜態(tài)對(duì)象兽泣。代碼如下
public class Singleton implements Serializable {
private static final Singleton INSTANCE=new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return INSTANCE;
}
}
3.3.1 破壞單例
上面的單例看起來(lái)有點(diǎn)薄弱绎橘,那么它能實(shí)現(xiàn)我們要的單利效果嗎?讓我們來(lái)測(cè)一下唠倦,畢竟不想當(dāng)測(cè)試的程序員不是一個(gè)好UI称鳞。
- 單線程模式
/**
* 單線程調(diào)用
*/
public void testWithOneThread() {
for (int i = 0; i < 10; i++) {
System.out.println(Singleton.getInstance());
}
}
public static void main(String[] args) {
SingletonTest singletonTest=new SingletonTest();
singletonTest.testWithOneThread();
}
輸出結(jié)果如下:
com.designPatterns.singleton.eager.Singleton@610455d6
com.designPatterns.singleton.eager.Singleton@610455d6
com.designPatterns.singleton.eager.Singleton@610455d6
com.designPatterns.singleton.eager.Singleton@610455d6
com.designPatterns.singleton.eager.Singleton@610455d6
com.designPatterns.singleton.eager.Singleton@610455d6
com.designPatterns.singleton.eager.Singleton@610455d6
com.designPatterns.singleton.eager.Singleton@610455d6
com.designPatterns.singleton.eager.Singleton@610455d6
com.designPatterns.singleton.eager.Singleton@610455d6
看起來(lái)運(yùn)行的很完美,但是這樣就完了嗎稠鼻?
我們嘗試從以下幾方面嘗試來(lái)打破單例
1. 多線程
2. 反射
3. 序列化和反序列化
ps:由于我們的類(lèi)未考慮要實(shí)現(xiàn)Cloneable接口冈止,故clone 的單利破壞不在討論范圍之內(nèi)
- 多線程
/**
* 多線程調(diào)用
*/
public void testWithMultiThread() {
ExecutorService pool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
pool.submit(() -> {
System.out.println(Singleton.getInstance());
});
}
}
public static void main(String[] args) {
SingletonTest singletonTest=new SingletonTest();
singletonTest.testWithMultiThread();
}
運(yùn)行結(jié)果如下:
com.designPatterns.singleton.eager.Singleton@68674054
com.designPatterns.singleton.eager.Singleton@68674054
com.designPatterns.singleton.eager.Singleton@68674054
com.designPatterns.singleton.eager.Singleton@68674054
com.designPatterns.singleton.eager.Singleton@68674054
com.designPatterns.singleton.eager.Singleton@68674054
com.designPatterns.singleton.eager.Singleton@68674054
com.designPatterns.singleton.eager.Singleton@68674054
com.designPatterns.singleton.eager.Singleton@68674054
com.designPatterns.singleton.eager.Singleton@68674054
我們可以看到餓漢模式實(shí)現(xiàn)的單利,在多線程情況下運(yùn)行良好候齿,static 對(duì)象只在初始化的時(shí)候被加載一次熙暴,這點(diǎn)是由JVM保證的
- 反射
/**
* 反射調(diào)用
* @throws Exception
*/
public void testWithReflect() throws Exception{
Constructor[] constructors = Singleton.class.getDeclaredConstructors();
Constructor defaultConstructor = constructors[0];
defaultConstructor.setAccessible(true);
Singleton singleton = (Singleton) defaultConstructor.newInstance(null);
System.out.println(Singleton.getInstance());
System.out.println(singleton);
}
public static void main(String[] args) throws Exception{
SingletonTest singletonTest=new SingletonTest();
singletonTest.testWithReflect();
}
com.designPatterns.singleton.eager.Singleton@610455d6
com.designPatterns.singleton.eager.Singleton@511d50c0
我們可以看到,通過(guò)反射調(diào)用構(gòu)造方法慌盯,可以得到兩個(gè)不同的對(duì)象周霉,餓漢單利在反射情況下不能良好運(yùn)行
- 序列化和反序列化
/**
* 序列化反序列化調(diào)用
*
* @throws Exception
*/
public void testWithStream() throws Exception {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("\\singleton"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("\\singleton")));) {
oos.writeObject(Singleton.getInstance());
Singleton singleton = (Singleton) ois.readObject();
System.out.println(Singleton.getInstance());
System.out.println(singleton);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
SingletonTest singletonTest = new SingletonTest();
singletonTest.testWithStream();
}
com.designPatterns.singleton.eager.Singleton@266474c2
com.designPatterns.singleton.eager.Singleton@2f4d3709
我們看到通過(guò)序列化和反序列化,我們也成功的打破了餓漢單利
润匙。
通過(guò)上面的實(shí)例诗眨,我們看到,餓漢模式在單線程及多線程環(huán)境下可以良好運(yùn)行孕讳。在反射匠楚、序列化和反序列化的情況下無(wú)法良好運(yùn)行。
3.3.2 序列化和反序列化的實(shí)現(xiàn)機(jī)制
當(dāng)我們將一個(gè)對(duì)象在磁盤(pán)上存儲(chǔ)厂财,或者網(wǎng)上傳輸?shù)臅r(shí)候芋簿,需要將其轉(zhuǎn)換成byte[],這個(gè)過(guò)程稱(chēng)之為序列化璃饱,其相反的過(guò)程為反序列化与斤。實(shí)現(xiàn)序列化的方式有兩種,實(shí)現(xiàn)Serializable\Externalizable 接口
,下面為其異同
類(lèi)目 | 是否要public constructor | 實(shí)例化的方式 | 自定義邏輯的方式 |
---|---|---|---|
Serializable | no | 查找最近父類(lèi)撩穿,調(diào)用父類(lèi)的無(wú)參構(gòu)造器來(lái)實(shí)例化磷支,逐個(gè)屬性賦值 |
定義私有readObject\writeObject\readResolve |
Externalizable | yes | 調(diào)用當(dāng)前類(lèi)的無(wú)參public constructor 實(shí)例化,調(diào)用readExternal為屬性賦 |
重寫(xiě)writeExternal/readExternal 方法 |
下面以Serializable為例來(lái)做個(gè)示范
public class Student implements Serializable{
private String name;
private int age;
private transient String addr;
private Student(){
System.out.println("構(gòu)造方法被調(diào)用了");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", addr='" + addr + '\'' +
'}';
}
/**
* 定制序列化
* @param oos
* @throws Exception
*/
private void writeObject(ObjectOutputStream oos)throws Exception{
System.out.println("writeObject 調(diào)用了");
oos.defaultWriteObject();
oos.writeObject((addr+"test"));
}
/**
* 定制反序列化
* @param ois
* @throws Exception
*/
private void readObject(ObjectInputStream ois)throws Exception{
System.out.println("readObject 調(diào)用了");
ois.defaultReadObject();
addr=(String) ois.readObject();
}
/**
* 替換readObject后的值
* @return
*/
private Object readResolve(){
System.out.println("readResolve 調(diào)用了");
return this;
}
private static Student initStudent(){
Student student=new Student();
student.setAddr("上海市");
student.setAge(20);
student.setName("zhangsan");
System.out.println("init student:"+student);
return student;
}
public static void main(String[] args) throws Exception{
Student student=initStudent();
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("\\sitx"));
//將對(duì)象轉(zhuǎn)為byte[] 輸出
oos.writeObject(student);
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(new File("\\sitx")));
//從byte[] 中讀取對(duì)象
Student std= (Student)ois.readObject();
System.out.println("readStudent:"+std);
}
}
輸出結(jié)果:
構(gòu)造方法被調(diào)用了
init student:Student{name='zhangsan', age=20, addr='上海市'}
writeObject 調(diào)用了
readObject 調(diào)用了
readResolve 調(diào)用了
readStudent:Student{name='zhangsan', age=20, addr='上海市test'}
我們可以看到readObject 實(shí)例化的時(shí)候食寡,并沒(méi)有調(diào)用我們私有的構(gòu)造器雾狈。同時(shí)readObject的調(diào)用順序要先于readResolve。同時(shí)我們發(fā)現(xiàn)了一個(gè)有趣的現(xiàn)象抵皱,Student類(lèi)中的三個(gè)方法readObject\writeObject\readResolve均為私有方法善榛,是怎么被外部調(diào)用的呢?
原來(lái)ObjectOutputStream.writeObject和ObjectInputStream.readObject的過(guò)程中呻畸,會(huì)通過(guò)先按照默認(rèn)的方式序列化/反序列化所有的非transient屬性移盆,然后再利用反射調(diào)用類(lèi)中的私有writeObject/readObject/readReSolve方法
。
源碼如下:
- 在ObjectStreamClass實(shí)例化的時(shí)候伤为,通過(guò)反射查找并初始化上面的幾個(gè)私有方法
if (serializable) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
if (isEnum) {
suid = Long.valueOf(0);
fields = NO_FIELDS;
return null;
}
if (cl.isArray()) {
fields = NO_FIELDS;
return null;
}
suid = getDeclaredSUID(cl);
try {
fields = getSerialFields(cl);
computeFieldOffsets();
} catch (InvalidClassException e) {
serializeEx = deserializeEx =
new ExceptionInfo(e.classname, e.getMessage());
fields = NO_FIELDS;
}
if (externalizable) {
cons = getExternalizableConstructor(cl);
} else {
cons = getSerializableConstructor(cl);
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class<?>[] { ObjectOutputStream.class },
Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class<?>[] { ObjectInputStream.class },
Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
}
domains = getProtectionDomains(cons, cl);
writeReplaceMethod = getInheritableMethod(
cl, "writeReplace", null, Object.class);
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
return null;
}
});
}
- 在ObjectOutputStream 中 invokeMethod
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
if (slotDesc.hasWriteObjectMethod()) {
PutFieldImpl oldPut = curPut;
curPut = null;
SerialCallbackContext oldContext = curContext;
if (extendedDebugInfo) {
debugInfoStack.push(
"custom writeObject data (class \"" +
slotDesc.getName() + "\")");
}
try {
curContext = new SerialCallbackContext(obj, slotDesc);
bout.setBlockDataMode(true);
slotDesc.invokeWriteObject(obj, this);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
} finally {
curContext.setUsed();
curContext = oldContext;
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
curPut = oldPut;
} else {
defaultWriteFields(obj, slotDesc);
}
}
}
void invokeWriteObject(Object obj, ObjectOutputStream out)
throws IOException, UnsupportedOperationException
{
requireInitialized();
if (writeObjectMethod != null) {
try {
writeObjectMethod.invoke(obj, new Object[]{ out });
} catch (InvocationTargetException ex) {
Throwable th = ex.getTargetException();
if (th instanceof IOException) {
throw (IOException) th;
} else {
throwMiscException(th);
}
} catch (IllegalAccessException ex) {
// should not occur, as access checks have been suppressed
throw new InternalError(ex);
}
} else {
throw new UnsupportedOperationException();
}
}
3.3.3 通過(guò)readResolve 來(lái)改善單例
通過(guò)上面的介紹咒循,大致可以了解readResolve的用法。下面我們用其來(lái)完善餓漢單例钮呀。
public class Singleton implements Serializable {
private static final Singleton INSTANCE=new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return INSTANCE;
}
private Object readResolve(){
return INSTANCE;
}
}
- 序列化和反序列化測(cè)試
/**
* 序列化反序列化調(diào)用
*
* @throws Exception
*/
public void testWithStream() {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("\\singleton"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("\\singleton")));) {
oos.writeObject(Singleton.getInstance());
Singleton singleton = (Singleton) ois.readObject();
System.out.println(Singleton.getInstance());
System.out.println(singleton);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
SingletonTest singletonTest = new SingletonTest();
singletonTest.testWithStream();
}
結(jié)果如下:
com.designPatterns.singleton.eager.Singleton@b4c966a
com.designPatterns.singleton.eager.Singleton@b4c966a
成功的防止了在序列化和反序列化情況下單利破壞剑鞍。
3.4 懶漢單例
懶漢單利誓琼,即用到的時(shí)候才實(shí)例化的單例人断。這里的懶指的是懶加載。
public class Singleton implements Serializable {
private static Singleton INSTANCE = null;
private Singleton() {
}
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
3.4.1 破壞單例
我們分別在單線程環(huán)境搀暑、多線程環(huán)境蚂四、反射光戈、序列化和反序列化的情況下獲取單例。
由于代碼與3.3.1 中相同遂赠,故只貼出運(yùn)行結(jié)果
- 單線程
com.designPatterns.singleton.lazy.Singleton@610455d6
com.designPatterns.singleton.lazy.Singleton@610455d6
com.designPatterns.singleton.lazy.Singleton@610455d6
com.designPatterns.singleton.lazy.Singleton@610455d6
com.designPatterns.singleton.lazy.Singleton@610455d6
com.designPatterns.singleton.lazy.Singleton@610455d6
com.designPatterns.singleton.lazy.Singleton@610455d6
com.designPatterns.singleton.lazy.Singleton@610455d6
com.designPatterns.singleton.lazy.Singleton@610455d6
com.designPatterns.singleton.lazy.Singleton@610455d6
- 多線程
com.designPatterns.singleton.lazy.Singleton@49fe0fd2
com.designPatterns.singleton.lazy.Singleton@49fe0fd2
com.designPatterns.singleton.lazy.Singleton@7c837e63
com.designPatterns.singleton.lazy.Singleton@49fe0fd2
com.designPatterns.singleton.lazy.Singleton@49fe0fd2
com.designPatterns.singleton.lazy.Singleton@358b5609
com.designPatterns.singleton.lazy.Singleton@49fe0fd2
com.designPatterns.singleton.lazy.Singleton@49fe0fd2
com.designPatterns.singleton.lazy.Singleton@49fe0fd2
com.designPatterns.singleton.lazy.Singleton@5bbb3bd2
- 反射
com.designPatterns.singleton.lazy.Singleton@610455d6
com.designPatterns.singleton.lazy.Singleton@511d50c0
- 序列化和反序列化
com.designPatterns.singleton.lazy.Singleton@b4c966a
com.designPatterns.singleton.lazy.Singleton@2f4d3709
可以看到久妆,懶漢模式只有在單線程環(huán)境下能良好運(yùn)行。在多線程跷睦、反射筷弦、序列化和反序列化環(huán)境下均無(wú)法保證單例。
當(dāng)然我們同樣可以通過(guò)定義readResolve方法來(lái)讓其在序列化和反序列的情況下正常運(yùn)行抑诸。由于與3.3.3 的代碼完全一致烂琴,此處不做演示。
值得注意的是蜕乡,懶漢模式實(shí)現(xiàn)了懶加載奸绷,這點(diǎn)是餓漢無(wú)法實(shí)現(xiàn)的
3.5 雙重鎖單利
既然餓漢模式實(shí)現(xiàn)了懶加載但是無(wú)法保證線程安全,那么有沒(méi)有一種即能實(shí)現(xiàn)懶加載层玲,又能保證線程安全的單利模式呢号醉?雙重鎖單利(double-check-singleton)就是這種單利反症。
所謂雙重鎖單利,即通過(guò)對(duì)代碼加鎖(可以是語(yǔ)言層面的synchronzied,也可以是jdk層面的 Lock)和雙重非空判斷的方式來(lái)實(shí)現(xiàn)單利畔派。
通過(guò)這種方式實(shí)現(xiàn)的單利铅碍,兼具線程安全及懶加載及性能。以下為代碼實(shí)現(xiàn)
private static volatile Singleton INSTANCE = null;
private Singleton() {
}
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
有兩點(diǎn)值得注意的地方
synchronized 加鎖的位置是出于性能的考慮(不加在方法上是為了保證互斥的最小性)
-
volatile關(guān)鍵字是為了禁止指令的重排序及保證可見(jiàn)性(防止多線程環(huán)境下父虑,拿到已分配內(nèi)存但未初始化的對(duì)象)
上面的第二點(diǎn)主要是因?yàn)閚ew Singleton() 這個(gè)步驟该酗,是一個(gè)非原子性操作。通過(guò)javap -c Singleton 查看字節(jié)碼:
public static com.designPatterns.singleton.doubleCheck.Singleton getInstance();
Code:
0: getstatic #2 // Field INSTANCE:Lcom/designPatterns/singleton/doubleCheck/Singleton;
3: ifnonnull 37
6: ldc #3 // class com/designPatterns/singleton/doubleCheck/Singleton
8: dup
9: astore_0
10: monitorenter
11: getstatic #2 // Field INSTANCE:Lcom/designPatterns/singleton/doubleCheck/Singleton;
14: ifnonnull 27
17: new #3 // class com/designPatterns/singleton/doubleCheck/Singleton
20: dup
21: invokespecial #4 // Method "<init>":()V
24: putstatic #2 // Field INSTANCE:Lcom/designPatterns/singleton/doubleCheck/Singleton;
27: aload_0
28: monitorexit
29: goto 37
32: astore_1
33: aload_0
34: monitorexit
35: aload_1
36: athrow
37: getstatic #2 // Field INSTANCE:Lcom/designPatterns/singleton/doubleCheck/Singleton;
40: areturn
我們可以看到 instance=new Singleton 分為幾步
開(kāi)辟對(duì)象空間-new
調(diào)用構(gòu)造函數(shù)初始化-invokespecial
-
為引用賦值-putstatic
步驟2和3 可能會(huì)發(fā)生指令的重排序士嚎,導(dǎo)致多線程環(huán)境下拿到未初始化的實(shí)例。
3.5.1 破壞單例
我們分別在單線程環(huán)境悔叽、多線程環(huán)境莱衩、反射、序列化和反序列化的情況下獲取單例娇澎。
由于代碼與3.3.1 中相同笨蚁,故只貼出運(yùn)行結(jié)果
- 單線程
com.designPatterns.singleton.doubleCheck.Singleton@610455d6
com.designPatterns.singleton.doubleCheck.Singleton@610455d6
com.designPatterns.singleton.doubleCheck.Singleton@610455d6
com.designPatterns.singleton.doubleCheck.Singleton@610455d6
com.designPatterns.singleton.doubleCheck.Singleton@610455d6
com.designPatterns.singleton.doubleCheck.Singleton@610455d6
com.designPatterns.singleton.doubleCheck.Singleton@610455d6
com.designPatterns.singleton.doubleCheck.Singleton@610455d6
com.designPatterns.singleton.doubleCheck.Singleton@610455d6
com.designPatterns.singleton.doubleCheck.Singleton@610455d6
- 多線程
com.designPatterns.singleton.doubleCheck.Singleton@3480e44e
com.designPatterns.singleton.doubleCheck.Singleton@3480e44e
com.designPatterns.singleton.doubleCheck.Singleton@3480e44e
com.designPatterns.singleton.doubleCheck.Singleton@3480e44e
com.designPatterns.singleton.doubleCheck.Singleton@3480e44e
com.designPatterns.singleton.doubleCheck.Singleton@3480e44e
com.designPatterns.singleton.doubleCheck.Singleton@3480e44e
com.designPatterns.singleton.doubleCheck.Singleton@3480e44e
com.designPatterns.singleton.doubleCheck.Singleton@3480e44e
com.designPatterns.singleton.doubleCheck.Singleton@3480e44e
- 反射
com.designPatterns.singleton.doubleCheck.Singleton@610455d6
com.designPatterns.singleton.doubleCheck.Singleton@511d50c0
- 序列化和反序列化
com.designPatterns.singleton.doubleCheck.Singleton@b4c966a
com.designPatterns.singleton.doubleCheck.Singleton@2f4d3709
可以看到,雙重鎖單例在單線程及多線程環(huán)境下均能很好的工作趟庄,但是在反射及序列化反序列化的情況下均無(wú)法正常工作括细。
3.6 內(nèi)部類(lèi)單例
所謂內(nèi)部類(lèi)單例,即通過(guò)內(nèi)部類(lèi)的方式來(lái)提供懶加載的單例戚啥。
public class Singleton {
private Singleton() {
}
private static class Holder {
private static Singleton singleton = new Singleton();
}
public static Singleton getInstance() {
return Holder.singleton;
}
}
值得注意的是:
- 實(shí)現(xiàn)了懶加載的單例
- 不用加鎖
- 外部類(lèi)可以訪問(wèn)內(nèi)部類(lèi)的私有變量(TODO)
3.6.1 破壞單例
我們分別在單線程環(huán)境奋单、多線程環(huán)境、反射猫十、序列化和反序列化的情況下獲取單例览濒。
由于代碼與3.3.1 中相同,故只貼出運(yùn)行結(jié)果
- 單線程
com.designPatterns.singleton.holder.Singleton@610455d6
com.designPatterns.singleton.holder.Singleton@610455d6
com.designPatterns.singleton.holder.Singleton@610455d6
com.designPatterns.singleton.holder.Singleton@610455d6
com.designPatterns.singleton.holder.Singleton@610455d6
com.designPatterns.singleton.holder.Singleton@610455d6
com.designPatterns.singleton.holder.Singleton@610455d6
com.designPatterns.singleton.holder.Singleton@610455d6
com.designPatterns.singleton.holder.Singleton@610455d6
com.designPatterns.singleton.holder.Singleton@610455d6
- 多線程
com.designPatterns.singleton.holder.Singleton@3480e44e
com.designPatterns.singleton.holder.Singleton@3480e44e
com.designPatterns.singleton.holder.Singleton@3480e44e
com.designPatterns.singleton.holder.Singleton@3480e44e
com.designPatterns.singleton.holder.Singleton@3480e44e
com.designPatterns.singleton.holder.Singleton@3480e44e
com.designPatterns.singleton.holder.Singleton@3480e44e
com.designPatterns.singleton.holder.Singleton@3480e44e
com.designPatterns.singleton.holder.Singleton@3480e44e
com.designPatterns.singleton.holder.Singleton@3480e44e
- 反射
com.designPatterns.singleton.holder.Singleton@610455d6
com.designPatterns.singleton.holder.Singleton@511d50c0
- 序列化和反序列化
com.designPatterns.singleton.holder.Singleton@b4c966a
com.designPatterns.singleton.holder.Singleton@2f4d3709
我們可以看到內(nèi)部類(lèi)單例在單線程及多線程環(huán)境下均能很好的運(yùn)行拖云,但是在反射和序列化反序列化的情況下無(wú)法保證單例贷笛。
3.7 枚舉單例
上面的幾種單例模式,不能同時(shí)滿(mǎn)足 懶加載宙项、線程安全乏苦、序列化反序列、反射的情況下單例尤筐。那么就沒(méi)有一種同時(shí)滿(mǎn)足以上幾種情況的單例嗎汇荐?
枚舉單例既能保證線程安全,又能保證序列化和反序列化單例叔磷,也能保證反射單例拢驾。但是無(wú)法懶加載。
public enum Singleton {
INSTANCE;
public static Singleton getInstance(){
return INSTANCE;
}
}
是不是特別簡(jiǎn)單改基,值得注意的是:
- jvm 會(huì)保證enum 構(gòu)造器的私有繁疤,故此處不用寫(xiě)私有構(gòu)造方法
3.7.1 破壞單例
我們分別在單線程環(huán)境咖为、多線程環(huán)境、反射稠腊、序列化和反序列化的情況下獲取單例躁染。
由于代碼與3.3.1 中相同,故只貼出運(yùn)行結(jié)果
- 單線程
INSTANCE
INSTANCE
INSTANCE
INSTANCE
INSTANCE/
INSTANCE
INSTANCE
INSTANCE
INSTANCE
INSTANCE
- 多線程
INSTANCE
INSTANCE
INSTANCE
INSTANCE
INSTANCE
INSTANCE
INSTANCE
INSTANCE
INSTANCE
INSTANCE
- 反射
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.designPatterns.singleton.SingletonTest.testWithReflect(SingletonTest.java:48)
at com.designPatterns.singleton.SingletonTest.main(SingletonTest.java:74)
- 序列化和反序列化
INSTANCE
INSTANCE
可以看到架忌,我們無(wú)法通過(guò)反射調(diào)用枚舉類(lèi)型的構(gòu)造方法吞彤。枚舉單利,在單線程叹放、多線程饰恕、反射、序列化反序列化的情況下均能很好的工作井仰。無(wú)需加鎖埋嵌,實(shí)現(xiàn)簡(jiǎn)單,是首推的實(shí)現(xiàn)單例的方式俱恶。
3.8 幾種單例的比對(duì)
單例類(lèi)型 | 能否懶加載 | 是否線程安全 | 是否加鎖 | 能否通過(guò)反射獲取新的實(shí)例 | 能否通過(guò)序列化和反序列化獲取新的實(shí)例 |
---|---|---|---|---|---|
餓漢模式 | 非懶加載 | 線程安全 | 不加鎖 | 是 | 否 |
懶漢模式 | 懶加載 | 線程不安全 | 不加鎖 | 是 | 否 |
雙重鎖單例 | 非懶加載 | 線程安全 | 加鎖 | 是 | 否 |
內(nèi)部類(lèi)單例 | 懶加載 | 線程安全 | 不加鎖 | 是 | 否 |
枚舉單例 | 非懶加載 | 線程安全 | 不加鎖 | 否 | 否 |
可以看到枚舉單例是我們實(shí)現(xiàn)起來(lái)最簡(jiǎn)單雹嗦,也是最高效的單例,不用去考慮私有構(gòu)造器合是,不用去考慮定制反序列化readResolve方法
了罪。
3.9 基于單例編寫(xiě)文件加載
假設(shè)有個(gè)需求需要加載資源文件,這種資源文件的加載是比較耗時(shí)的聪全,我們肯定只希望加載一次泊藕,嘗試用枚舉單例解決這個(gè)問(wèn)題。
public enum PropertiesConfig {
//實(shí)例
CONFIG;
//配置文件存儲(chǔ)集合
private ConcurrentHashMap sources = new ConcurrentHashMap();
PropertiesConfig() {
Properties properties = new Properties();
try {
properties.load(PropertiesConfig.class.getResourceAsStream("application.properties"));
} catch (IOException e) {
e.printStackTrace();
}
properties.entrySet().forEach(p -> sources.put(p.getKey(), p.getValue()));
}
private static PropertiesConfig getInstance() {
return CONFIG;
}
private Object getPropertiesByKey(String key) {
return sources.get(key);
}
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
pool.submit(() -> {
System.out.println(PropertiesConfig.getInstance().getPropertiesByKey("url"));
});
}
}
}
4.總結(jié)
本文開(kāi)始荔烧,我們開(kāi)啟了新的話題-設(shè)計(jì)模式吱七。我們先是總結(jié)了設(shè)計(jì)模式的分類(lèi),然后著重剖析了單例模式鹤竭,分析了單例模式的實(shí)現(xiàn)踊餐。
but 本文的重點(diǎn)不是單例模式的實(shí)現(xiàn),而是單例模式的破壞臀稚,以及小tips-序列化和反序列自定義
吝岭。希望通本文,大家都能對(duì)單例的種種有些許新的感悟吧寺。
由于技術(shù)水平所限窜管,文章難免有不足之處,歡迎大家指出稚机。希望每位讀者都能有新的收獲幕帆,我們下一篇文章-設(shè)計(jì)模式初探-創(chuàng)建型模式之工廠模式 見(jiàn)
.....
參考文章
- head first 設(shè)計(jì)模式