版權聲明:本文為博主原創文章,未經博主允許不得轉載。
如果問一個碼農最先接觸到的設計模式是什么,單例設計模式一定最差也是“之一”。
單例,Singleton,保證內存中只有一份實例對象存在。
問:為什么要有單例?
答:此對象可能會為成千上百的線程所用,當然不希望不希望每次使用都要new一個新的 對象,也可能是使用不多但是初始化需要消耗大量內存,也可能需要消耗大量cpu運算,又可能僅僅是為了為實例內進行數據管理同步。總之,單例只希望初始化 一遍,且唯一存在,在實際開發中所用甚多。
問:單例既然只存在一份成員變量,且外部主要也是調用其方法。為什么不能用static方法和static變量來代替單例呢?
答:static方法和變量理論可行。但是:第一,靜態方法不能實現多態,不能繼承,不符合Java的設計理念;第二,單例不用的時候,因為他是一個對象而存在,可以提供銷毀對象的方法,但是如果用static就永遠留在了內存中。
單例實現:
首先想到的自然是最簡單的實現方式,私有化構造函數,然后將該實例保存為static final,也就是餓漢式
- public class Singleton {
- private static final Singleton SINGLETON_INSTANCE = new Singleton();
- private Singleton(){
- }
- public Singleton getInstance(){
- return SINGLETON_INSTANCE;
- }
- }
缺點:類初始化即創建對象,即使沒有想要獲取單例,仍然會初始化該實例對象,占用內存。
然后我們想到的是懶漢式,到需要單例的時候再初始化
- public class Singleton {
- private static Singleton sSingleton;
- private Singleton(){
- }
- public Singleton getInstance(){
- if(sSingleton == null){
- sSingleton = new Singleton();
- }
- return sSingleton;
- }
- }
缺點:多線程獲取該單例時,若果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 ,已經快接近真理
- public class Singleton {
- private static Singleton sSingleton;
- private Singleton(){
- }
- public Singleton getInstance(){
- if(sSingleton == null){
- synchronized (Singleton.class) {
- if(sSingleton == null){
- sSingleton = new Singleton();
- }
- }
- }
- return sSingleton;
- }
- }
保證只有單例為空,才進入同步實例化,實例化一次以后,不會再進入同步鎖,貌似已經完美解決。
故事還沒有完。。。
首先我們要了解一下jvm的內存模型,自己用qq截圖在桌面上截圖,然后用塗鴉工具畫的,畫的丑,湊合看
Java中的內存數據存在主寄存器中。由於cpu的執行效率比內存的讀取效率快很多,所以為了提高效率使用cpu高速緩存,每個線程會對自己線程中用到的變量,在自己的線程緩存內存中留下一個副本,但這樣就可能造成線程的memory和main memory不同步,從而造成臟讀。
好了祭出Volatile關鍵字。volatile,詞典釋意為爆炸的,不穩定的。用該關鍵字修飾一個變量,意在告訴jvm該變量是線程不安全的。首先在當線程需要訪問該變量時,jvm將拒絕該線程memory中保留主memory的duplicate,需要讀寫直接到main memory中讀取,避免臟讀。只需要用Volatile修飾sSingleton就可以了,犧牲了一點效率,安全和性能總是相斥的嘛。
- public class Singleton {
- private static volatile Singleton sSingleton;
- private Singleton(){
- }
- public Singleton getInstance(){
- if(sSingleton == null){
- synchronized (Singleton.class) {
- if(sSingleton == null){
- sSingleton = new Singleton();
- }
- }
- }
- return sSingleton;
- }
- }
需要注意的是:volatile只能保證多線程讀取的是同一塊內存的數據,所以如果操作必須是原子操作,比如賦值操作是原子操作,但 voilatile int n; n=n+1就不是。如果不是原子操作,就只能乖乖使用syncnized同步鎖了。syncnized線程鎖也可以實現線程安全,他的原理是獲取監視器,然后將thread memory和main memory中數據同步,然后執行代碼塊,最后再將改變寫回main memory,最后釋放監視器,原理不同,更費效率,但是可以保證非原子操作的線程安全。
最后說一句volatile是java1.5加入的,之前貌似是有bug的。