【簡介】
單例模式,屬於創建類型的一種常用的軟件設計模式。通過單例模式的方法創建的類在當前進程中只有一個實例(根據需要,也有可能一個線程中屬於單例,如:僅線程上下文內使用同一個實例)
【場景】
現實中有意思的場景可以查閱小編之前總結的一篇博客:找對象需要單例模式嗎?
適用單例的條件:
一、某個類只能有一個實例
二、是它必須自行創建這個實例
三、是它必須自行向整個系統提供這個實例。
單例具體實現要求:
一、是單例模式的類只提供私有的構造函數
二、是類定義中含有一個該類的靜態私有對象
三、是該類提供了一個靜態的公有的函數用於創建或獲取它本身的靜態私有對象。
【示例】
一、餓漢式,私有靜態變量初始化方式(簡單易用,能滿足大部分要求)
說明:類加載到內存後,就實例化一個單例,jvm保證線程安全(缺點:不管用到與否,類裝載時就會完成實例化)
public class Mar1 {
private static Mar1 INSTANCE = new Mar1();
private Mar1(){}
public static Mar1 getInstance(){
return INSTANCE;
}
public static void main(String[] args) {
Mar1 mar1 = Mar1.getInstance();
Mar1 mar11 = Mar1.getInstance();
System.out.println(mar1 == mar11);
}
}
二、懶漢式,鎖方法
說明:需要用到的時候再進行實例創建(缺點:併發帶來效率低下的問題)
注:volatile關鍵字可以規避指令重排帶來的不安全性(idea中可以查看編輯指令)
public class Mar4 {
private static volatile Mar4 INSTANCE;
private Mar4(){}
public static synchronized Mar4 getInstance(){
if(INSTANCE == null){
//測試併發加入的睡眠
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mar4();
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i<100;i++){
new Thread(()->{
System.out.println(Mar4.getInstance().hashCode());
}).start();
}
}
}
三、懶漢式,局部鎖,雙重判定
說明:需要用到的時候再進行實例創建,相對於二的效率得到了一些提升
public class Mar6 {
private static volatile Mar6 INSTANCE;
private Mar6(){}
public static Mar6 getInstance(){
if(INSTANCE == null){
synchronized(Mar6.class){
if(INSTANCE == null) {
//測試併發加入的睡眠
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mar6();
}
}
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i<100;i++){
new Thread(()->{
System.out.println(Mar6.getInstance().hashCode());
}).start();
}
}
}
四、懶漢式,內部類實現
說明:類加載的時候不會去加載內部類,當使用到調用的時候纔會進行實例化
public class Mar7 {
private Mar7(){}
private static class Mar7Holder{
private static Mar7 INSTANCE = new Mar7();
}
public static Mar7 getInstance(){
return Mar7Holder.INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i<100;i++){
new Thread(()->{
System.out.println(Mar7.getInstance().hashCode());
}).start();
}
}
}
五、枚舉方式(effectice java書中提到的)
說明:不僅可以解決線程安全還可以防止反序列化
public enum Mar8 {
INSTANCE;
public static void main(String[] args) {
for (int i = 0; i<100;i++){
new Thread(()->{
System.out.println(Mar8.INSTANCE.hashCode());
}).start();
}
}
}
【總結】
通過學習各種形式的單例模式,對單例又有了一個更加深刻的認識。
1.利用java中的特性來解決(內部類、枚舉類)
2.使用鎖的時候要在保證安全的前提控制鎖的粒度
3.通過學習單例對以後code更有幹勁
4.激發了自己瞭解其它模式更加濃厚的興趣與愛好