在講單例模式之前,我想先試講一個例子,在什么程序中,我們都需要讀取property配置文件,通過Java去解析這個文件,拿出我們想要的數值,所以我們很快很快就可以寫出下面一個類去讀取文件:
public class LoadProperty { private String name; public LoadProperty() { read(); } public void read() { Properties property = new Properties(); InputStream input = null; input = this.getClass().getResourceAsStream("config.properties"); try { property.load(input); }catch(Exception e) { e.printStackTrace(); } this.name = property.getProperty("name"); } public String getName() { return name; } public void setName(String name) { this.name = name; } } class TestLoadProperty { public static void main(String[] args) { LoadProperty loadProperty = new LoadProperty(); System.out.println(loadProperty.getName()); } }
從這個例子我們可以發現一個問題,如果我的程序要很多次調用這個配置文件呢,那么我是不是每次都是要new出一個實例出來呢,這樣的話我們的程序跑起來的話就可以很慢,很吃力,內存就很快溢出了,一個相同的東西,我們要多次去產生它的實例,這多麻煩啊,如果只產生一個實例那該多好啊,所有就有了單例模式的出現了。
單例模式:
定義:保證一個類僅有一個實例,並且提供一個訪問它的全局訪問點。
如何去寫一個單例模式:我們可以這么去想想,為什么我們的類的實例可以被多次的創建,就是因為他的構造方法是public的,誰想訪問就訪問,現在我們要控制這個權限,就必須收回它的使用權,就是說這個是用權是歸我所有的,所以我們很自然的將這個構造方法設置成private,這樣的話就沒人訪問了。這樣很自然也就引出了一個問題,怎么去訪問它,看看我們隊單例模式的定義的吧,“提供一個訪問它的全局訪問點”,這個就說明了我們要提供一個方法去訪問它,我們通常把這個方法寫作:getInstance(),但是你又會問,我連這個類的實例都創建不了,我怎么去使用這個方法呢,哈哈,我們就利用static這個屬性,通過這個類直接去訪問這個方法,不需要去創建這個實例,有了這個思想,我們就開始寫單例模式吧!!!
單例模式有兩種:餓漢式和懶漢式
餓漢式:
顧名思義,餓漢,就是在創建對象實例的時候比較急,餓了嘛,就在類加載的時候就創建對象的實例了。
看看下面的演示:
public class Singleton { //用static修飾是為了在類加載的時候就創建實例 private static Singleton instance = new Singleton(); //私有的構造方法,收回了別人的調用權限,使用權歸自己所有 private Singleton() { } //static修飾符幫助別人可以通過類直接調用這個方法 public static Singleton getInstance() { return instance; } } class TestSingleton { public static void main(String[] args) { Singleton singleton1 = Singleton.getInstance(); Singleton singleton2 = Singleton.getInstance(); //打印的肯定是true,因為訪問的是同一個實例 System.out.println(singleton1 == singleton2); } }
從這個餓漢式我們很明顯的看到它的實現方法,在類加載的時候就創建好這個對象的實例,然后就不再創建了,內存中一直存在這個實例,我們通過getInstance方法去訪問,return的也一直是這個實例,從而就實現了單例模式。
懶漢式:
說白了就是懶,就是等到別人要這個類的實例的時候采取創建,不叫它做事的時候就一定不會做。
我們來看看它的實現方式:
public class Singleton { //用一個null值的變量來存放實例,因為懶,在類加載的時候還沒去創建實例 private static Singleton instance = null; //私有的構造方法 private Singleton() { } //調用這個方法的時候首先看看是不是第一次調用,第一次調用肯定沒有創建這個實例,是的話就創建這個實例 //如果不是第一次的話,就證明就這個instance中有值了,就可以直接直接返回了 public static Singleton getInstance() { if(instance == null) instance = new Singleton(); return instance; } } class TestSingleton { public static void main(String[] args) { Singleton singleton1 = Singleton.getInstance(); Singleton singleton2 = Singleton.getInstance(); //打印的肯定是true,因為訪問的是同一個實例 System.out.println(singleton1 == singleton2); } }
從這個懶漢式我們可以分析得到,我們先不創建這個類的實例,等到要使用的時候我們才來創建,創建完了我們就一直放在那里,別人要用的時候就直接把這個實例扔過去就ok了。
上面就基本講完了單例模式,下面我們還要補充一點知識:
延遲加載的思想:
單例模式的懶漢式的實現方式就體現了延遲加載的思想,就是說,一開始不要加載資源或者數據,一直等,等啊,等到馬上要使用這個資源或者數據的時候,才去加載,所以也叫Lazy Load,這在實際開發中是一種很常見的思想,盡可能的節約資源。
緩存的思想:
懶漢式的單例模式也有這個緩存的思想在里面,如果某些資源是存儲在外部的,但是我們又很經常的使用它,我們每次從外部中去找,然后使用,這個的工作量是很大的,緩存的思想就是將這些資源放在內存中每次操作的時候就直接在內存中找就是了,是一種典型的空間換時間的方案。
單例模式的優缺點:
1.時間和空間
懶漢式就是典型的時間換空間的思想,每次都要去判斷,看看是否需要實例,餓漢式是典型的空間換時間,當類加載的時候就創建好實例了,不管你用不用,反正就放在那里了,調用的時候就不能訪問了,直接返回給你。
2.線程安全
從線程安全的角度上來說,餓漢式是安全的,但是懶漢式是不安全的,打個比方說,現在有線程A和線程B同時去調用getInstance方法,就可能出現線程並發的情況,如果所示:
從上面就可以分析出這個懶漢式是線程不安全的,下面我們演示一個線程安全的懶漢式:
//調用這個方法的時候首先看看是不是第一次調用,第一次調用肯定沒有創建這個實例,是的話就創建這個實例 //如果不是第一次的話,就證明就這個instance中有值了,就可以直接直接返回了 public static synchronized Singleton getInstance() { if(instance == null) instance = new Singleton(); return instance; }
其實就是在方法的前面加上synchronized,這個就是同步的標記,這樣一來,線程就安全了
最后我們將我們最開始那個讀取文件的類改成單例模式的:
public class LoadProperty { private String name; private static LoadProperty instance = null; private LoadProperty() { read(); } public void read() { Properties property = new Properties(); InputStream input = null; input = this.getClass().getResourceAsStream("config.properties"); try { property.load(input); }catch(Exception e) { e.printStackTrace(); } this.name = property.getProperty("name"); } public static synchronized LoadProperty getInstance() { if(instance == null) instance = new LoadProperty(); return instance; } public String getName() { return name; } public void setName(String name) { this.name = name; } } class TestLoadProperty { public static void main(String[] args) { LoadProperty loadProperty = LoadProperty.getInstance(); System.out.println(loadProperty.getName()); } }