關(guān)鍵
如何使單例模式遇到多線程是安全的。
立即加載/"餓漢模式"
立即加載就是使用類的時候已經(jīng)將對象創(chuàng)建完畢范舀,常見的實現(xiàn)辦法就是直接new實例化挡爵。而立即加載在中文語境看來火窒,有"急"的意思,所以也叫"餓漢模式"冒嫡。
public class MyObject {
// 立即加載方式==餓漢模式
/** Field myObject */
private static MyObject myObject = new MyObject();
/**
* Constructs MyObject
*
*/
private MyObject() {}
/**
* Method getInstance
*
*
* @return
*
* 此代碼版本為立即加載拇勃,此版本代碼的缺點是不能有其他實例變量,
* 因此getinstance()方法沒有同步孝凌,所以可能出現(xiàn)非線程安全問題方咆。
*/
public static MyObject getInstance() {
return myObject;
}
}
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}
public class Run {
/**
* Method main
*
*
* @param args
*/
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.start();
t2.start();
t3.start();
}
}
/*result:
1108767984
1108767984
1108767984
*/
運行結(jié)果顯示hashCode為同一個值,說明對象時同一個對象蟀架,也就實現(xiàn)了立即加載型單例模式瓣赂。
延遲加載/懶漢模式
延遲加載就是在調(diào)用get()方法是實例才被創(chuàng)建,常見的實現(xiàn)辦法就是在get()方法中進(jìn)行new實例化操作片拍。延遲加載又叫懶漢模式煌集。
餓漢模式時會出現(xiàn)多個實例,在多線程環(huán)境下這是與單例模式相背離的穆碎。
下面的代碼是完全錯誤的牙勘,不能實現(xiàn)保持單例的狀態(tài)。
public class MyObject {
/** Field myObject */
private static MyObject myObject;
/**
* Constructs MyObject
*
*/
private MyObject() {}
/**
* Method getInstance
*
*
* @return
*/
public static MyObject getInstance() {
try {
if (myObject != null) {}
else {
// 模仿在創(chuàng)建對象時的一些準(zhǔn)備工作
Thread.sleep(3000);
myObject = new MyObject();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
}
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}
public class Run {
/**
* Method main
*
*
* @param args
*/
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.start();
t2.start();
t3.start();
}
}
/*result:
1127673581
1516995504
1905381423
*/
此實驗會出現(xiàn)取出多個實例的情況所禀,這就是錯誤的單例模式方面。
延遲加載的解決方案。
添加sybchronized關(guān)鍵字 ==pass==
在實例化方法中添加synchronized關(guān)鍵字,即可實現(xiàn)得到同一實例色徘,但此方法運行效率非常低恭金,是同步運行的。
public static synchronized MyObject getInstance() {
try {
if (myObject != null) { }
else {
// 模仿在創(chuàng)建對象時的一些準(zhǔn)備工作
Thread.sleep(3000);
myObject = new MyObject();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
/*result:
1127673581
1127673581
1127673581
*/
使用同步代碼塊 ==pass==
此方法有所改進(jìn)褂策,但效率依舊比較低横腿。
public static MyObject getInstance() {
try {
synchronized (MyObject.class) {
if (myObject != null) {}
else {
// 模仿在創(chuàng)建對象時的一些準(zhǔn)備工作
Thread.sleep(3000);
myObject = new MyObject();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
/*result:
853747565
853747565
853747565
*/
針對某些重點代碼單獨同步 ==pass==
此方法運行效率較高。但多線程情況下無法實現(xiàn)只取一個實例對象斤寂。
public static MyObject getInstance() {
try {
if (myObject != null) { }
else {
// 模仿在創(chuàng)建對象時的一些準(zhǔn)備工作
Thread.sleep(3000);
synchronized (MyObject.class) {
myObject = new MyObject();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
/*result:
1127673581
936272565
726367991
*/
DCL雙檢查鎖機(jī)制耿焊。
最后步驟中使用DCL雙檢查鎖機(jī)制來實現(xiàn)多線程環(huán)境下延遲加載的單例模式,正確且效率高遍搞。
public static MyObject getInstance() {
try {
if (myObject != null) { }
else {
// 模仿在創(chuàng)建對象時的一些準(zhǔn)備工作
Thread.sleep(3000);
synchronized (MyObject.class) {
if (myObject == null) {
myObject = new MyObject();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
/*result:
1108767984
1108767984
1108767984
*/
使用靜態(tài)內(nèi)置類實現(xiàn)單例模式
DCL可以解決多線程單例模式的非線程安全問題罗侯,但是也有其他方法也能實現(xiàn)。
public class MyObject {
/**
* Constructs MyObject
*
*/
private MyObject() {}
/**
* Method getInstance
*
*
* @return
*/
public static MyObject getInstance() {
return MyObjectHandler.myObject;
}
/**
* Class MyObjectHandler
*
*
* @version 1.0, 18/05/05
* @author tz
*/
private static class MyObjectHandler {
/** Field myObject */
private static MyObject myObject = new MyObject();
}
}
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}
public class Run {
/**
* Method main
*
*
* @param args
*/
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.start();
t2.start();
t3.start();
}
}
/*result:
264320363
264320363
264320363
*/
上面代碼證明使用內(nèi)置內(nèi)部類的方法也可以實現(xiàn)懶漢模式下多線程的單實例模式溪猿。
序列化和反序列化的單例模式實現(xiàn)
靜態(tài)內(nèi)置類可以達(dá)到線程安全的目的钩杰,但是如果遇到序列化對象時纫塌,使用默認(rèn)的方法運行得到的結(jié)果還是多例的。
public class MyObject implements Serializable {
/** Field serialVersionUID */
private static final long serialVersionUID = 888L;
/**
* Constructs MyObject
*
*/
private MyObject() {}
/**
* Method getInstance
*
*
* @return
*/
public static MyObject getInstance() {
return MyObjectHandler.myObject;
}
/**
* Class MyObjectHandler
*
*
* @version Enter version here..., 18/05/05
* @author Enter your name here...
*/
private static class MyObjectHandler {
/** Field myObject */
private static final MyObject myObject = new MyObject();
}
}
public class SaveAndRead {
/**
* Method main
*
*
* @param args
*/
public static void main(String[] args) {
try {
MyObject myObject = MyObject.getInstance();
FileOutputStream fosRef = new FileOutputStream(new File("myObjectFile.txt"));
ObjectOutputStream oosRef = new ObjectOutputStream(fosRef);
oosRef.writeObject(myObject);
oosRef.close();
fosRef.close();
System.out.println(myObject.hashCode());
} catch (IOException e) {
e.printStackTrace();
}
try {
FileInputStream fisRef = new FileInputStream(new File("myObjectFile.txt"));
ObjectInputStream iosRef = new ObjectInputStream(fisRef);
MyObject myObject = (MyObject) iosRef.readObject();
iosRef.close();
fisRef.close();
System.out.println(myObject.hashCode());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
/*result:
1836019240
2093631819
*/
上述程序反應(yīng)了序列化對象的情況下,內(nèi)置內(nèi)部類不能實現(xiàn)單例模式讲弄,解決方法就是使用反序列化措左。
ublic class MyObject implements Serializable {
/** Field serialVersionUID */
private static final long serialVersionUID = 888L;
/**
* Constructs MyObject
*
*/
private MyObject() {}
/**
* Method getInstance
*
*
* @return
*/
public static MyObject getInstance() {
return MyObjectHandler.myObject;
}
protected Object readResolve() {
System.out.println("調(diào)用了readResolve方法!");
return MyObjectHandler.myObject;
}
/**
* Class MyObjectHandler
*
*
* @version Enter version here..., 18/05/05
* @author Enter your name here...
*/
private static class MyObjectHandler {
/** Field myObject */
private static final MyObject myObject = new MyObject();
}
}
/*result:
1836019240
調(diào)用了readResolve方法避除!
1836019240
*/
反序列化后即實現(xiàn)單例怎披。
使用static代碼塊實現(xiàn)單例模式
靜態(tài)代碼塊中的代碼在使用類的時候就已經(jīng)執(zhí)行了,所以可以應(yīng)用靜態(tài)代碼塊的這個特性來實現(xiàn)單例設(shè)計模式
public class MyObject {
/** Field instance */
private static MyObject instance = null;
static {
instance = new MyObject();
}
/**
* Constructs MyObject
*
*/
private MyObject() { }
/**
* Method getInstance
*
*
* @return
*/
public static MyObject getInstance() {
return instance;
}
}
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(MyObject.getInstance().hashCode());
}
}
}
public class Run {
/**
* Method main
*
*
* @param args
*/
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.start();
t2.start();
t3.start();
}
}
/*result:
868591785
868591785
868591785
868591785
868591785
868591785
868591785
868591785
868591785
868591785
868591785
868591785
868591785
868591785
868591785
*/
使用enum枚舉數(shù)據(jù)類型實現(xiàn)單例模式
枚舉enum和靜態(tài)代碼塊的特性相似瓶摆,在使用枚舉類時構(gòu)造方法會自動被調(diào)用钳枕,也可以應(yīng)用其這個特性實現(xiàn)單例設(shè)計模式。