作者:冰河
星球:http://m6z.cn/6aeFbs
博客:https://binghe.gitcode.host
文章彙總:https://binghe.gitcode.host/md/all/all.html
源碼地址:https://github.com/binghe001/java-simple-design-patterns/tree/master/java-simple-design-singleton
沉澱,成長,突破,幫助他人,成就自我。
- 本章難度:★★☆☆☆
- 本章重點:介紹創建Java單例對象的七種方式,重點掌握哪些創建方式是線程安全的,哪些方式是線程不安全的,並能夠在實際項目中靈活運用設計模式,編寫可維護的代碼。
大家好,我是冰河~~
今天給大家介紹《Java極簡設計模式》的第01章,單例設計模式(Singleton),用最短的篇幅講述設計模式最核心的知識,好了,開始今天的內容。
單例設計模式
看幾個單例對象的示例代碼,其中有些代碼是線程安全的,有些則不是線程安全的,需要大家細細品味,這些代碼也是冰河本人在高併發環境下測試驗證過的。
- 代碼一:SingletonExample1
這個類是懶漢模式,並且是線程不安全的
package io.binghe.concurrency.example.singleton;
/**
* @author binghe
* @version 1.0.0
* @description 懶漢模式,單例實例在第一次使用的時候進行創建,這個類是線程不安全的
*/
public class SingletonExample1 {
private SingletonExample1(){}
private static SingletonExample1 instance = null;
public static SingletonExample1 getInstance(){
//多個線程同時調用,可能會創建多個對象
if (instance == null){
instance = new SingletonExample1();
}
return instance;
}
}
- 代碼二:SingletonExample2
餓漢模式,單例實例在類裝載的時候進行創建,是線程安全的
package io.binghe.concurrency.example.singleton;
/**
* @author binghe
* @version 1.0.0
* @description 餓漢模式,單例實例在類裝載的時候進行創建,是線程安全的
*/
public class SingletonExample2 {
private SingletonExample2(){}
private static SingletonExample2 instance = new SingletonExample2();
public static SingletonExample2 getInstance(){
return instance;
}
}
- 代碼三:SingletonExample3
懶漢模式,單例實例在第一次使用的時候進行創建,這個類是線程安全的,但是這個寫法不推薦
package io.binghe.concurrency.example.singleton;
/**
* @author binghe
* @version 1.0.0
* @description 懶漢模式,單例實例在第一次使用的時候進行創建,這個類是線程安全的,但是這個寫法不推薦
*/
public class SingletonExample3 {
private SingletonExample3(){}
private static SingletonExample3 instance = null;
public static synchronized SingletonExample3 getInstance(){
if (instance == null){
instance = new SingletonExample3();
}
return instance;
}
}
- 代碼四:SingletonExample4
懶漢模式(雙重鎖同步鎖單例模式),單例實例在第一次使用的時候進行創建,但是,這個類不是線程安全的!!!!!
package io.binghe.concurrency.example.singleton;
/**
* @author binghe
* @version 1.0.0
* @description 懶漢模式(雙重鎖同步鎖單例模式)
* 單例實例在第一次使用的時候進行創建,這個類不是線程安全的
*/
public class SingletonExample4 {
private SingletonExample4(){}
private static SingletonExample4 instance = null;
//線程不安全
//當執行instance = new SingletonExample4();這行代碼時,CPU會執行如下指令:
//1.memory = allocate() 分配對象的內存空間
//2.ctorInstance() 初始化對象
//3.instance = memory 設置instance指向剛分配的內存
//單純執行以上三步沒啥問題,但是在多線程情況下,可能會發生指令重排序。
// 指令重排序對單線程沒有影響,單線程下CPU可以按照順序執行以上三個步驟,但是在多線程下,如果發生了指令重排序,則會打亂上面的三個步驟。
//如果發生了JVM和CPU優化,發生重排序時,可能會按照下面的順序執行:
//1.memory = allocate() 分配對象的內存空間
//3.instance = memory 設置instance指向剛分配的內存
//2.ctorInstance() 初始化對象
//假設目前有兩個線程A和B同時執行getInstance()方法,A線程執行到instance = new SingletonExample4(); B線程剛執行到第一個 if (instance == null){處,
//如果按照1.3.2的順序,假設線程A執行到3.instance = memory 設置instance指向剛分配的內存,此時,線程B判斷instance已經有值,就會直接return instance;
//而實際上,線程A還未執行2.ctorInstance() 初始化對象,也就是說線程B拿到的instance對象還未進行初始化,這個未初始化的instance對象一旦被線程B使用,就會出現問題。
public static SingletonExample4 getInstance(){
if (instance == null){
synchronized (SingletonExample4.class){
if(instance == null){
instance = new SingletonExample4();
}
}
}
return instance;
}
}
線程不安全分析如下:
當執行instance = new SingletonExample4();這行代碼時,CPU會執行如下指令:
1.memory = allocate() 分配對象的內存空間
2.ctorInstance() 初始化對象
3.instance = memory 設置instance指向剛分配的內存
單純執行以上三步沒啥問題,但是在多線程情況下,可能會發生指令重排序。
指令重排序對單線程沒有影響,單線程下CPU可以按照順序執行以上三個步驟,但是在多線程下,如果發生了指令重排序,則會打亂上面的三個步驟。
如果發生了JVM和CPU優化,發生重排序時,可能會按照下面的順序執行:
1.memory = allocate() 分配對象的內存空間
3.instance = memory 設置instance指向剛分配的內存
2.ctorInstance() 初始化對象
假設目前有兩個線程A和B同時執行getInstance()方法,A線程執行到instance = new SingletonExample4(); B線程剛執行到第一個 if (instance == null){處,如果按照1.3.2的順序,假設線程A執行到3.instance = memory 設置instance指向剛分配的內存,此時,線程B判斷instance已經有值,就會直接return instance;而實際上,線程A還未執行2.ctorInstance() 初始化對象,也就是說線程B拿到的instance對象還未進行初始化,這個未初始化的instance對象一旦被線程B使用,就會出現問題。
- 代碼五:SingletonExample5
懶漢模式(雙重鎖同步鎖單例模式)單例實例在第一次使用的時候進行創建,這個類是線程安全的,使用的是 volatile + 雙重檢測機制來禁止指令重排達到線程安全
package io.binghe.concurrency.example.singleton;
/**
* @author binghe
* @version 1.0.0
* @description 懶漢模式(雙重鎖同步鎖單例模式)
* 單例實例在第一次使用的時候進行創建,這個類是線程安全的
*/
public class SingletonExample5 {
private SingletonExample5(){}
//單例對象 volatile + 雙重檢測機制來禁止指令重排
private volatile static SingletonExample5 instance = null;
public static SingletonExample5 getInstance(){
if (instance == null){
synchronized (SingletonExample5.class){
if(instance == null){
instance = new SingletonExample5();
}
}
}
return instance;
}
}
- 代碼六:SingletonExample6
餓漢模式,單例實例在類裝載的時候(使用靜態代碼塊)進行創建,是線程安全的
package io.binghe.concurrency.example.singleton;
/**
* @author binghe
* @version 1.0.0
* @description 餓漢模式,單例實例在類裝載的時候進行創建,是線程安全的
*/
public class SingletonExample6 {
private SingletonExample6(){}
private static SingletonExample6 instance = null;
static {
instance = new SingletonExample6();
}
public static SingletonExample6 getInstance(){
return instance;
}
}
- 代碼七:SingletonExample7
枚舉方式進行實例化,是線程安全的,此種方式也是線程最安全的
package io.binghe.concurrency.example.singleton;
/**
* @author binghe
* @version 1.0.0
* @description 枚舉方式進行實例化,是線程安全的,此種方式也是線程最安全的
*/
public class SingletonExample7 {
private SingletonExample7(){}
public static SingletonExample7 getInstance(){
return Singleton.INSTANCE.getInstance();
}
private enum Singleton{
INSTANCE;
private SingletonExample7 singleton;
//JVM保證這個方法絕對只調用一次
Singleton(){
singleton = new SingletonExample7();
}
public SingletonExample7 getInstance(){
return singleton;
}
}
}
好了,今天的內容就到這兒吧,相信大家對單例設計模式有了全新的認識,我是冰河,我們下期見~~