單例設計模式
單例設計模式
概念:單例對象的類必須保證只有⼀個實例存在。
適⽤場景: 單例模式只允許創建⼀個對象,因此節省內存,加快對象訪問速度,因此對象需要被公⽤的場合適合使⽤,如多個模塊使⽤同⼀個數據源連接對象等等。如:
- 需要頻繁實例化然後銷毀的對象。
- 創建對象時耗時過多或者耗資源過多,但⼜經常⽤到的對象。
- 有狀態的⼯具類對象。
- 頻繁訪問數據庫或⽂件的對象。
常⻅寫法:
public class Singleton {
/**
* 優點:沒有線程安全問題,簡單
* 缺點:提前初始化會延⻓類加載器加載類的時間;如果不使⽤會浪費內存空間;不能傳遞參數
*/
private static final Singleton instance = new Singleton();
private Singleton(){};
public static Singleton getInstance(){
return instance;
}
}
餓漢式:在實例化類的時候就會創建對象。
優點:
- 會提前初始化,你調用的時候就不需要再去初始化。性能是好的。
- 沒有線程安全問題,簡單
缺點:
- 提前初始化會延⻓類加載器加載類的時間;
- 如果不使⽤會浪費內存空間,因為,即使不用它,它也會在那;
- 不能傳遞參數。
public class Singleton{
/**
* 優點:解決線程安全,延遲初始化( Effective Java推薦寫法)
*/
private Singleton(){}
public static Singleton getInstance () {
return Holder.SINGLE_TON;
}
private static class Holder{
private static final Singleton SINGLE_TON = new Singleton();
}
}
懶漢式:延遲初始化的手段
調用它的時候,會去初始化;不調用它的時候,就不初始化。
優點:
解決線程安全,延遲初始化( Effective Java推薦寫法)
public class Singleton {
private volatile static Singleton uniqueSingleton;
private Singleton() {
}
public Singleton getInstance() {
if (null == uniqueSingleton) {//第一重檢查
synchronized (Singleton.class) {//第一重加鎖
if (null == uniqueSingleton) {//第二重檢查
uniqueSingleton = new Singleton();//創建對象
}
}
}
return uniqueSingleton;
}
}
雙重檢查鎖原理:
第一次判斷為空以後,第一次加鎖。
因為加鎖不是一個原子操作,在加鎖的過程中,uniqueSingleton對象可能已經被初始化掉了,所以加鎖完畢以後,還需要去判斷一下,uniqueSingleton對象是不是為空?
加鎖後,進行第二次檢驗(檢查)
如果uniqueSingleton對象為空,進行創建對象。
注意:
volatile
- 會保證可見性
- 不會保證原子性。
具體原理:
加了一個log的前置指令(即內存屏障),會保證屏障後面的指令不會放到屏障之前,也不會把屏障之前的指令放到屏障之後。在執行到屏障的時候,一定會保證屏障之前的指令全執行完了。
進行寫操作的時候:
- 有緩存:寫操作會導致緩存失效;
- 無緩存:將緩存里的修改,直接寫入到緩存里。
volatile
的作用:
阻止指令重排序;
加入
volatile
,會加入一個內存屏障。加入內存屏障後順序就不能變了。是禁止指令重排序。
保證可見性;
平時操作時,會有一個主內存和一個工作內存的。
如果不加volatile,數據是放在工作內存的,進行操作的時候,需要從工作內存刷到主內存。加了volatile,相當於不用工作內存,直接用主內存。相當於多個線程對一個對象操作的時候,多個線程對結果是可見的。
為什麼考慮指令重排序?
volatile指令重排序
在執⾏程序時,為了提供性能,處理器和編譯器常常會對指令進⾏重排序,但是不能隨意重排序,不是你 想怎麼排序就怎麼排序,它需要滿⾜以下兩個條件:
- 在單線程環境下不能改變程序運⾏的結果;
- 存在數據依賴關係的不允許重排序
指令排序
uniqueSingleton = new Singleton();
- 分配內存空間
- 初始化對象
- 將對象指向剛分配的內存空間
但是有些編譯器為了性能的原因,可能會將第⼆步和第三步進⾏重排序,順序就成了:
- 分配內存空間
- 將對象指向剛分配的內存空間
- 初始化對象
現在考慮重排序後,兩個線程發⽣了以下調⽤:
Time Thread A Thread B T1 檢查到uniqueSingleton為空 T2 獲取鎖 T3 再次檢查到uniqueSingleton為 空 T4 為uniqueSingleton分配內存空 間 T5 將uniqueSingleton指向內存空 間 T6 檢查到uniqueSingleton不為空 T7 訪問uniqueSingleton(此時對 象還未完成初始化) T8 初始化uniqueSingleton 在這種情況下,T7時刻線程B對uniqueSingleton的訪問,訪問的是⼀個
初始化未完成
的對象。使⽤了
volatile
關鍵字後,重排序被禁⽌
,所有的寫(write)操作都將發⽣在讀(read)操作之前。
單例模式的破壞
Singleton sc1 = Singleton.getInstance();
Singleton sc2 = Singleton.getInstance();
System.out.println(sc1); // sc1,sc2是同⼀個對象
System.out.println(sc2);
/*通過反射的⽅式直接調⽤私有構造器*/
Class clazz = (Class) Class.forName("com.le arn.example.Singleton");
Constructor c = clazz.getDeclaredConstructor(null);
c.setAccessible(true); // 跳過權限檢查
Singleton sc3 = c.newInstance();
Singleton sc4 = c.newInstance();
System.out.println("通過反射的⽅式獲取的對象sc3:" + sc3); // sc3,sc 4不是同⼀個對象
System.out.println("通過反射的⽅式獲取的對象sc4:" + sc4);
//以上代碼是說明單例是可以被破壞的
//防⽌反射獲取多個對象的漏洞 (即防止單例被破壞的解決辦法。)
private Singleton() {
if (null != SingletonClassInstance.instance)
throw new RuntimeException();
}
spring中bean的單例
a. 當多個⽤戶同時請求⼀個服務時,容器會給每⼀個請求分配⼀個線程,這時多個線程會並發執⾏該請求對應的業務邏輯(成員⽅法)。此時就要注意了,如果該處理邏輯中有對單例狀態的修改(體現為該單例的成員屬性),則必須考慮線程同步問題。
有狀態就是有數據存儲功能
。有狀態對象(Stateful Bean)就是有實例變量的對象
,可以保存數據,是⾮線程安全的
。在不同⽅法調⽤間不保留任何狀態。
⽆狀態就是⼀次操作,不能保存數據
。⽆狀態對象(Stateless Bean),就是沒有實例變量的對象 . 不能保存數據,是不變類,是線程安全的。
b. 實現
public abstract class AbstractBeanFactory implements Configurable
BeanFactory{
/**
* 充當了Bean實例的緩存,實現⽅式和單例註冊表相同
*/
private final Map singletonCache=new HashMap();
public Object getBean(String name)throws BeansException{
return getBean(name,null,null);
}
...
public Object getBean(String name,Class requiredType,Object[] args)throws BeansException{
//對傳⼊的Bean name稍做處理,防⽌傳⼊的Bean name名有⾮法字符(或則做轉碼)
String beanName=transformedBeanName(name);
Object bean=null;
//⼿⼯檢測單例註冊表
Object sharedInstance=null;
//使⽤了代碼鎖定同步塊,原理和同步⽅法相似,但是這種寫法效率更⾼
synchronized(this.singletonCache){
sharedInstance=this.singletonCache.get(beanName);
}
if(sharedInstance!=null){
...
//返回合適的緩存Bean實例
bean=getObjectForSharedInstance(name,sharedInstance);
}else{...
//取得Bean的定義
RootBeanDefinition mergedBeanDefinition=getMergedBeanDefinition(beanName,false);
...
//根據Bean定義判斷,此判斷依據通常來⾃於組件配置⽂件的單例屬性開關
//<bean id="date" class="java.util.Date" scope="singleton"/>
//如果是單例,做如下處理
if(mergedBeanDefinition.isSingleton()){
synchronized(this.singletonCache){//再次檢測單例註冊表
sharedInstance=this.singletonCache.get(beanName);
if(sharedInstance==null){
...
try {
//真正創建Bean實例
sharedInstance=createBean(beanName,mergedBeanDefinition,args);
//向單例註冊表註冊Bean實例
addSingleton(beanName,sharedInstance);
}catch (Exception ex) {
...
}finally{
...
}
}
}
bean=getObjectForSharedInstance(name,sharedInstance);
}
//如果是⾮單例,即prototpye,每次都要新創建⼀個Bean實例
//<bean id="date" class="java.util.Date" scope="prototype"/>
else{
bean=createBean(beanName,mergedBeanDefinition,args);
}
}
...
return bean;
}
}