為什么使用單例模式
需要確保某個(gè)類只要一個(gè)對(duì)象,或創(chuàng)建一個(gè)類需要消耗的資源過多,如訪問IO和數(shù)據(jù)庫操作等,這時(shí)就需要考慮使用單例模式了雹拄。
使用單例模式需要注意的關(guān)鍵點(diǎn)
- 將構(gòu)造函數(shù)訪問修飾符設(shè)置為private
- 通過一個(gè)靜態(tài)方法或者枚舉返回單例類對(duì)象
- 確保單例類的對(duì)象有且只有一個(gè),特別是在多線程環(huán)境下
- 確保單例類對(duì)象在反序列化時(shí)不會(huì)重新構(gòu)建對(duì)象
單例模式的幾種寫法
1. 餓漢式
/**
* 餓漢式實(shí)現(xiàn)單例模式
*/
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
2. 懶漢式
/**
* 懶漢式實(shí)現(xiàn)單例模式
*/
public class Singleton {
private static Singleton instance;
private Singleton() {
}
// synchronized方法,多線程情況下保證單例對(duì)象唯一
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
getInstance()方法中添加了synchronized關(guān)鍵字掌呜,使其變成一個(gè)同步方法滓玖,目的是為了在多線程環(huán)境下保證單例對(duì)象唯一。
優(yōu)點(diǎn): 只有在使用時(shí)才會(huì)實(shí)例化單例质蕉,一定程度上節(jié)約了資源势篡。
缺點(diǎn): 第一次加載時(shí)要立即實(shí)例化,反應(yīng)稍慢模暗。每次調(diào)用getInstance()方法都會(huì)進(jìn)行同步禁悠,這樣會(huì)消耗不必要的資源。這種模式一般不建議使用兑宇。
3. DCL(Double CheckLock)實(shí)現(xiàn)單例
/**
* DCL實(shí)現(xiàn)單例模式
*/
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
// 兩層判空碍侦,第一層是為了避免不必要的同步
// 第二層是為了在null的情況下創(chuàng)建實(shí)例
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
優(yōu)點(diǎn): 資源利用率高,既能夠在需要的時(shí)候才初始化實(shí)例,又能保證線程安全瓷产,同時(shí)調(diào)用getInstance()方法不進(jìn)行同步鎖站玄,效率高。
缺點(diǎn): 第一次加載時(shí)稍慢濒旦,由于Java內(nèi)存模型的原因偶爾會(huì)失敗株旷。在高并發(fā)環(huán)境下也有一定的缺陷,雖然發(fā)生概率很小疤估。
DCL模式是使用最多的單例模式實(shí)現(xiàn)方式灾常,除非代碼在并發(fā)場景比較復(fù)雜或者JDK 6以下版本使用霎冯,否則铃拇,這種方式基本都能滿足需求。
4. 靜態(tài)內(nèi)部類
/**
* 靜態(tài)內(nèi)部類實(shí)現(xiàn)單例模式
*/
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
/**
* 靜態(tài)內(nèi)部類
*/
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}
}
第一次加載Singleton類時(shí)不會(huì)初始化instance沈撞,只有在第一次調(diào)用getInstance()方法時(shí)慷荔,虛擬機(jī)會(huì)加載SingletonHolder類,初始化instance缠俺。
這種方式既保證線程安全显晶,單例對(duì)象的唯一,也延遲了單例的初始化壹士,推薦使用這種方式來實(shí)現(xiàn)單例模式磷雇。
5. 枚舉單例
/**
* 枚舉實(shí)現(xiàn)單例模式
*/
public enum SingletonEnum {
INSTANCE;
public void doSomething() {
System.out.println("do something");
}
}
默認(rèn)枚舉實(shí)例的創(chuàng)建是線程安全的,即使反序列化也不會(huì)生成新的實(shí)例躏救,任何情況下都是一個(gè)單例唯笙。
優(yōu)點(diǎn): 簡單!
6. 容器實(shí)現(xiàn)單例
import java.util.HashMap;
import java.util.Map;
/**
* 容器類實(shí)現(xiàn)單例模式
*/
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<String, Object>();
public static void regsiterService(String key, Object instance) {
if (!objMap.containsKey(key)) {
objMap.put(key, instance);
}
}
public static Object getService(String key) {
return objMap.get(key);
}
}
SingletonManager可以管理多個(gè)單例類型盒使,使用時(shí)根據(jù)key獲取對(duì)象對(duì)應(yīng)類型的對(duì)象崩掘。這種方式可以通過統(tǒng)一的接口獲取操作,隱藏了具體實(shí)現(xiàn)少办,降低了耦合度苞慢。