2-1 單例模式
阿新 • • 發佈:2018-03-02
pub ron 實踐 script 線程 性能 image log 生命
單例模式
一、引入
皇帝與臣子
一個類只能生成一個對象(皇帝),其他所有類對這個對象的依賴都是同一個,體現到代碼上如下:
/** * @Author: Zephyr * @Description: 定義一個私有的構造器,Emperor自己可以new一個對象,但其他類不能new當前對象,其他類只能通過靜態的getInstance方法獲取Emperor對象 * @Date: 2018/3/1 19:09 */ public class Emperor { private static final Emperor emperor= new Emperor(); //私有構造器 private Emperor() { } //靜態實例獲取 public static Emperor getInstance(){ return emperor; } public static String say(){ return "無事退朝"; } } /** * 每天臣子都會面對同一個皇帝 */ class Minister{ public static void main(String[] args) { for (int i = 0; i < 3; i++) { Emperor e = Emperor.getInstance(); System.out.println("上朝第"+(i+1)+"天,皇帝說"+e.say()); } } }
- 執行結果:
二、定義
- 關鍵詞:【自行實例化】、【Cloneable接口】
定義:
Ensure a class has only one instance,and provide a global point of access to it
確保每個類只有一個實例,自行實例化並向整個系統提供這個實例
代碼清單7-3
/** * @Author: Zephyr * @Description: 代碼清單7-3 單例模式通用代碼(餓漢式:類加載時就創建實例,線程安全) * @Date: 2018/3/1 19:49 */ public class HungrySingleton { private static final HungrySingleton s = new HungrySingleton(); //私有構造器,其他類無法new當前類實例 private HungrySingleton() { } //通過此方法向外部提供當前類實例 public static final HungrySingleton getInstance(){ return s; } //類中的其他方法,盡量用static //todo 為什麽盡量用static修飾? public static void otherMethod(){ } }
三、應用
優點
- 減少內存開支(比如:需要頻繁創建或者銷毀一個對象)
- 減少系統的性能開銷(比如:產生一個對象需要較多的資源)
- 避免對資源的多重占用(比如:IO流操作過程中,只存在一個inputStream實例,則可以避免對同一個文件同時進行操作)
- 可以用於在系統中設置全局訪問點,優化和共享資源訪問(比如:系統全局的計數器,整個系統共用)
缺點
- 拓展困難(沒有接口,只能通過修改代碼實現修改)
- 對測試不利(單例類沒有完成則無法進行測試,沒有接口也不能使用mock方式創建虛擬對象)
- 與單一職責原則相違背(一個類應該只實現一個邏輯,而不應該關心其是否單例,是否單例應該取決於業務環境)
使用場景舉例
- 要求生成唯一序列號的環境
- 整個系統中需要一個共享數據點或者共享數據(比如:全局計數器)
- 創建一個對象需要太多的資源
- 需要定義大量的靜態常量和靜態方法(比如:工具類,也可以直接全部聲明為static)
註意事項
在高並發環境下可能引發線程安全問題,推薦使用餓漢式(解決方式:添加關鍵字synchronized)
/**
* @Author: Zephyr
* @Description: 代碼清單7-4 線程不安全的單例(懶漢式:用到時再創建實例,線程不安全)
* @Date: 2018/3/1 20:15
*/
public class LazySingleton {
private static LazySingleton s = null;
private LazySingleton() {
}
//public static final 【synchronized】 LazySingleton getInstance(){
public static final LazySingleton getInstance(){
if(s==null){
s = new LazySingleton();
}
return s;
}
}
- 單例類不要實現Clonable接口(clone()方法的調用不通過構造器,因此即使僅有私有構造器,如果實現了Clonable接口,仍然可以通過clone()方法復制對象從而產生多個實例)
四、擴展
有上限的多例模式:有固定數量實例的類。
核心邏輯:首先通過靜態代碼塊生成指定數量的對象存入私有列表,再通過getInstance()方法隨機返回列表中的某個對象
對應代碼實現如圖(代碼中使用了ArrayList存放實例,考慮到線程安全問題可以使用):
/**
* @Author: Zephyr
* @Description: 代碼清單7-6 臣子參拜皇帝的過程(有上限的多例模式)
* @Date: 2018/3/1 20:29
*/
public class EmperorExpend {
//對象屬性
private String name;
//設置實例數量
private static Integer maxNum=3;
//私有構造器
private EmperorExpend(String name) {
this.name = name;
}
//對象(皇帝)列表
private static List<EmperorExpend> emperorList = new ArrayList<EmperorExpend>();
//靜態代碼塊,用於產生固定數量的對象(皇帝)
static{
for (Integer i = 0; i < maxNum; i++) {
emperorList.add(new EmperorExpend("皇帝0"+(i+1)));
}
}
//隨機參見一個皇帝
public static EmperorExpend getInstance(){
Random r = new Random();
return emperorList.get(r.nextInt(maxNum));
}
//皇帝發話
public String say() {
return name;
}
}
class MinisterExpend{
public static void main(String[] args) {
//有10個大臣
int ministerNum=10;
for (int i = 0; i < ministerNum; i++) {
EmperorExpend e = EmperorExpend.getInstance();
System.out.println("第"+(i+1)+"個大臣參拜的是:"+e.say());
}
}
}
效果如圖:
五、最佳實踐
單例模式是23個設計模式中最簡單的模式,應用也非常廣泛。Spring中默認每個Bean都是單例的,優點是這樣Spring就可以管理這些Bean的生命周期。
如果設置了非單例模式(Prototype類型),則Bean初始化後的管理交給J2EE容器,Spring容器不再跟蹤管理這些Bean的生命周期
2-1 單例模式