Java單例你所不知道的事,與Volatile關鍵字有染


如果問一個碼農最先接觸到的設計模式是什么,單例設計模式一定最差也是“之一”。

單例,Singleton,保證內存中只有一份實例對象存在。



問:為什么要有單例?

答:此對象可能會為成千上百的線程所用,當然不希望不希望每次使用都要new一個新的 對象,也可能是使用不多但是初始化需要消耗大量內存,也可能需要消耗大量cpu運算,又可能僅僅是為了為實例內進行數據管理同步。總之,單例只希望初始化 一遍,且唯一存在,在實際開發中所用甚多。



問:單例既然只存在一份成員變量,且外部主要也是調用其方法。為什么不能用static方法和static變量來代替單例呢?

答:static方法和變量理論可行。但是:第一,靜態方法不能實現多態,不能繼承,不符合Java的設計理念;第二,單例不用的時候,因為他是一個對象而存在,可以提供銷毀對象的方法,但是如果用static就永遠留在了內存中。




單例實現:

首先想到的自然是最簡單的實現方式,私有化構造函數,然后將該實例保存為static final,也就是餓漢式

 

[java] view plain copy
  1. public class Singleton {  
  2.     private static final Singleton SINGLETON_INSTANCE = new Singleton();  
  3.       
  4.     private Singleton(){  
  5.           
  6.     }  
  7.       
  8.     public Singleton getInstance(){  
  9.         return SINGLETON_INSTANCE;  
  10.     }  
  11. }  

缺點:類初始化即創建對象,即使沒有想要獲取單例,仍然會初始化該實例對象,占用內存。




然后我們想到的是懶漢式,到需要單例的時候再初始化

 

[java] view plain copy
  1. public class Singleton {  
  2.     private static Singleton sSingleton;  
  3.       
  4.     private Singleton(){  
  5.           
  6.     }  
  7.       
  8.     public Singleton getInstance(){  
  9.         if(sSingleton == null){  
  10.             sSingleton = new Singleton();  
  11.         }  
  12.         return sSingleton;  
  13.     }  
  14. }  

缺點:多線程獲取該單例時,若果t1執行到了class Singleton {  

  •     private static Singleton sSingleton;  
  •       
  •     private Singleton(){  
  •           
  •     }  
  •       
  •     public Singleton getInstance(){  
  •         synchronized (Singleton.class) {  
  •             if(sSingleton == null){  
  •                 sSingleton = new Singleton();  
  •             }  
  •         }  
  •         return sSingleton;  
  •     }  
  • }  

缺點:每次執行getInstance的時候,都會進入同步代碼塊,保證線程同步,但效率低!




繼續,改為double-check lock ,已經快接近真理

 

[java] view plain copy
  1. public class Singleton {  
  2.     private static Singleton sSingleton;  
  3.       
  4.     private Singleton(){  
  5.           
  6.     }  
  7.       
  8.     public Singleton getInstance(){  
  9.         if(sSingleton == null){  
  10.             synchronized (Singleton.class) {  
  11.                 if(sSingleton == null){  
  12.                     sSingleton = new Singleton();  
  13.                 }  
  14.             }  
  15.         }  
  16.         return sSingleton;  
  17.     }  
  18. }  

保證只有單例為空,才進入同步實例化,實例化一次以后,不會再進入同步鎖,貌似已經完美解決。



故事還沒有完。。。



首先我們要了解一下jvm的內存模型,自己用qq截圖在桌面上截圖,然后用塗鴉工具畫的,畫的丑,湊合看



Java中的內存數據存在主寄存器中。由於cpu的執行效率比內存的讀取效率快很多,所以為了提高效率使用cpu高速緩存,每個線程會對自己線程中用到的變量,在自己的線程緩存內存中留下一個副本,但這樣就可能造成線程的memory和main memory不同步,從而造成臟讀。



好了祭出Volatile關鍵字。volatile,詞典釋意為爆炸的,不穩定的。用該關鍵字修飾一個變量,意在告訴jvm該變量是線程不安全的。首先在當線程需要訪問該變量時,jvm將拒絕該線程memory中保留主memory的duplicate,需要讀寫直接到main memory中讀取,避免臟讀。只需要用Volatile修飾sSingleton就可以了,犧牲了一點效率,安全和性能總是相斥的嘛。

 

[java] view plain copy
  1. public class Singleton {  
  2.     private static volatile Singleton sSingleton;  
  3.       
  4.     private Singleton(){  
  5.           
  6.     }  
  7.       
  8.     public Singleton getInstance(){  
  9.         if(sSingleton == null){  
  10.             synchronized (Singleton.class) {  
  11.                 if(sSingleton == null){  
  12.                     sSingleton = new Singleton();  
  13.                 }  
  14.             }  
  15.         }  
  16.         return sSingleton;  
  17.     }  
  18. }  


需要注意的是:volatile只能保證多線程讀取的是同一塊內存的數據,所以如果操作必須是原子操作,比如賦值操作是原子操作,但 voilatile int n; n=n+1就不是。如果不是原子操作,就只能乖乖使用syncnized同步鎖了。syncnized線程鎖也可以實現線程安全,他的原理是獲取監視器,然后將thread memory和main memory中數據同步,然后執行代碼塊,最后再將改變寫回main memory,最后釋放監視器,原理不同,更費效率,但是可以保證非原子操作的線程安全。


最后說一句volatile是java1.5加入的,之前貌似是有bug的。



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM