單例模式結合多線程技術
1.餓漢模式/“立即加載”
立即加載就是使用類的時候已經將對象創建完畢,常見的實現方法是直接new 實例化。
1.1 立即加載型單例模式
創建MyObject.java:
public class MyObject {
private static MyObject myObject = new MyObject();
private MyObject(){
}
public static MyObject getInstance(){
return myObject;
}
}
創建MyThread.java:
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}
創建測試類Run.java:
public class Run {
public static void main(String[] args) {
MyThread a1 = new MyThread();
MyThread a2 = new MyThread();
MyThread a3 = new MyThread();
a1.start();
a2.start();
a3.start();
}
}
最後的運行結果如下,可見是同一個對象,即實現了立即加載型單例模式。
1097280301
1097280301
1097280301
2.延遲加載/“懶漢模式”
延遲加載就是在調用get()方法時實例才被創建,常見的實現方法就是在get()方法中進行new實例化,延遲加載也被稱作"懶漢模式"
修改MyObject類如下:
public class MyObject {
private static MyObject myObject;
private MyObject(){
}
public static MyObject getInstance(){
//延遲加載
if(myObject == null){
myObject = new MyObject();
}
return myObject;
}
}
最後的運行結果如下,能夠看到多線程環境中並不是同一個單例
980142845
1553816201
418194775
- 上面的延遲加載/"餓漢模式"在多線程的環境中不能實現單例,解決方案如下:
修改MyObject類如下:
public class MyObject {
private static MyObject myObject;
private MyObject(){
}
synchronized public static MyObject getInstance(){
//延遲加載
if(myObject == null) {
myObject = new MyObject();
}
return myObject;
}
}
1553816201
1553816201
1553816201
最後的運行結果雖然實現了是取的同一個實例,但是效率卻非常的低,嘗試同步代碼塊和直接同步方法是一樣的,效率也提高不了多少,最後採用DCL雙檢查鎖機制
DCL雙檢查鎖機制
修改MyObject類如下:
public class MyObject {
private static MyObject myObject;
private MyObject(){
}
public static MyObject getInstance(){
//延遲加載
if(myObject == null) {
synchronized(MyObject.class){
if(myObject == null){
myObject = new MyObject();
}
}
}
return myObject;
}
}
最後的運行結果仍然是單例的,且提升了效率
1230761675
1230761675
1230761675
使用靜態內置類實現單例模式
DCL可以解決多線程單例模式的非線程安全問題,當然其他的辦法也能達到同樣的效果
修改MyObject類如下:
public class MyObject {
private MyObject(){
}
public static MyObject getInstance(){
return MyObjectHandler.myObject;
}
private static class MyObjectHandler{
private static MyObject myObject = new MyObject();
}
}
最後的運行結果如下:
964250181
964250181
964250181
序列化與反序列化對象的單例模式實現
靜態內置類可以達到線程安全問題,但如果遇到序列化對象時,使用默認的方式運行得到的結果還是多例的,解決辦法就是在反序列化中使用readResolve()方法
public class MyObject implements Serializable {
private MyObject(){
}
public static MyObject getInstance(){
return MyObjectHandler.myObject;
}
private static class MyObjectHandler{
private static MyObject myObject = new MyObject();
}
protected Object readResolve(){
return MyObjectHandler.myObject;
}
}
創建測試類:
public class Run {
public static void main(String[] args) {
try {
MyObject myObject = MyObject.getInstance();
FileOutputStream fosRef = new FileOutputStream(new File("MyObject.txt"));
ObjectOutputStream fo = new ObjectOutputStream(fosRef);
fo.writeObject(myObject);
fo.close();
fosRef.close();
System.out.println(myObject.hashCode());
FileInputStream flsRef = new FileInputStream("MyObject.txt");
ObjectInputStream ioRef = new ObjectInputStream(flsRef);
MyObject m = (MyObject)ioRef.readObject();
ioRef.close();
flsRef.close();
System.out.println(m.hashCode());
}catch (Exception e){
e.printStackTrace();
}
}
}
最後的運行結果顯示兩者是單例的。
325040804
325040804
使用static代碼塊來實現單例模式(靜態代碼塊中的代碼在使用類的時候就已經執行了)
修改MyObject類如下:
public class MyObject {
private static MyObject instan = null;
static {
instan = new MyObject();
}
private MyObject(){
}
public static MyObject getInstance(){
return instan;
}
}
創建Run.java類:
public class Run {
public static void main(String[] args) {
MyThread a1 = new MyThread();
MyThread a2 = new MyThread();
MyThread a3 = new MyThread();
a1.start();
a2.start();
a3.start();
}
}
最後的運行結果如下:
2049367639
2049367639
2049367639
使用Enum枚舉數據類型實現單例模式
枚舉Enum和靜態代碼塊的特性相似,在使用枚舉類時,構造方法會被自動調用。即把MyObject類定義爲枚舉類