單例模式
1、簡介
單例模式是設計模式中最簡單的形式之一。這一模式的目的是使得類的一個物件成為系統中的唯一例項。要實現這一點,可以從客戶端對其進行例項化開始。因此需要用一種只允許生成物件類的唯一例項的機制,“阻止”所有想要生成物件的訪問。使用工廠方法來限制例項化過程。這個方法應該是靜態方法(類方法),因為讓類的例項去生成另一個唯一例項毫無意義。
2、應用場景
舉一個例子,網站的計數器,一般也是採用單例模式實現,如果你存在多個計數器,每一個使用者的訪問都重新整理計數器的值,這樣的話你的實計數的值是難以同步的。但是如果採用單例模式實現就不會存在這樣的問題,而且還可以避免執行緒安全問題。同樣多執行緒的執行緒池的設計一般也是採用單例模式,這是由於執行緒池需要方便對池中的執行緒進行控制
同樣,對於一些應用程式的日誌應用,或者web開發中讀取配置檔案都適合使用單例模式,如HttpApplication 就是單例的典型應用。
3、優缺點
優點:
- 在記憶體中只有一個物件,節省記憶體空間;
- 避免頻繁的建立銷燬物件,可以提高效能;
- 避免對共享資源的多重佔用,簡化訪問;
- 為整個系統提供一個全域性訪問點。
缺點:
- 不適用於變化頻繁的物件;
- 濫用單例將帶來一些負面問題,如為了節省資源將資料庫連線池物件設計為的單例類,可能會導致共享連線池物件的程式過多而出現連線池溢位;
- 如果例項化的物件長時間不被利用,系統會認為該物件是垃圾而被回收,這可能會導致物件狀態的丟失;
4、實現方式
4.1 餓漢式
/**
* 餓漢式
* 類載入到記憶體後,就例項化一個單例,JVM保證執行緒安全
* 簡單實用,推薦使用!
* 唯一缺點:不管用到與否,類裝載時就完成例項化
*/
public class Mgr01 {
private static final Mgr01 INSTANCE = new Mgr01();
private Mgr01() {};
public static Mgr01 getInstance() {
return INSTANCE;
}
public static void main(String[] args) {
Mgr01 m1 = Mgr01.getInstance();
Mgr01 m2 = Mgr01.getInstance();
System.out.println(m1 == m2);
}
}
在開發中,推薦使用這種方式,簡單易用,如果追求更高的要求,可以參考以下懶漢式的實現方式。
4.2 懶漢式
/**
* lazy loading
* 也稱懶漢式
* 雖然達到了按需初始化的目的,但卻帶來執行緒不安全的問題
* 可以通過synchronized解決,但也帶來效率下降
*/
public class Mgr06 {
private static volatile Mgr06 INSTANCE; //JIT
private Mgr06() {
}
public static Mgr06 getInstance() {
if (INSTANCE == null) {
//雙重檢查
synchronized (Mgr06.class) {
if(INSTANCE == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mgr06();
}
}
}
return INSTANCE;
}
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->{
System.out.println(Mgr06.getInstance().hashCode());
}).start();
}
}
}
雙重檢查,同時對部分程式碼進行鎖定,效率上有點影響,在看另外一種實現方式
/**
* 靜態內部類方式
* JVM保證單例
* 載入外部類時不會載入內部類,這樣可以實現懶載入
*/
public class Mgr07 {
private Mgr07() {
}
private static class Mgr07Holder {
private final static Mgr07 INSTANCE = new Mgr07();
}
public static Mgr07 getInstance() {
return Mgr07Holder.INSTANCE;
}
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->{
System.out.println(Mgr07.getInstance().hashCode());
}).start();
}
}
}
實際開發中,可以根據專案的業務場景進行選擇。