1、單例模式介紹 單例模式是應用最廣泛的模式之一,也是可以說是初級工程師唯一會用的設計模式。在應用這一模式的時候,單例對象的類必須保證只有一個實例存在。許多時候整個系統只要一個全局對象,這樣有利于我們協調系統整體行為。如在一個應用中,應該只有一個ImageLoader實例,這個ImageLoader中又包含有線程池、緩存系統、網絡請求等,很消耗資源,因此,沒有理由讓它有多個實例。這種情況就是單例模式的使用場景。 2、單例模式的定義 確保類只有一個實例,而且自行實例化并向整個系統提供這個實例 3、使用場景 確保類只有一個對象的場景 4、UML類圖 略 5、單例模式的多種實現 (1)餓漢模式 這是單例模式的最簡單的一種寫法,在具體介紹之前我們寫一個簡單的例子,這樣也方便后面介紹單例模式的其他寫發。 例如:一個公司只有一個CEO、幾個VP、無數個員工,例子很簡單。下面我們一點點的實現
普通員工類:Staff.java
package com.example.singleton;public class Staff { public void work() { //干活 }}副總裁類:VP.java
package com.example.singleton;public class VP extends Staff { @Override public void work() { // TODO Auto-generated method stub super .work(); //管理下面的經理 }}CEO類:CEO.java
package com.example.singleton;public class CEO extends Staff { PRivate static final CEO mCeo= new CEO(); // 構造函數私有化(構造方法的私有化是單例模式的核心) private CEO() { } /** * 方法一 * 餓漢單例模式 * * @author HP * */ public static CEO getCeo () { return mCeo ; } @Override public void work() { // TODO Auto-generated method stub super .work(); // 管理 vp }}公司類:Company.java
package com.example.singleton;import java.util.ArrayList;import java.util.List;/** * 公司類 * @author HP * */public class Company { private List<Staff> allStaffs=new ArrayList<>(); public void addStaff(Staff per) { allStaffs.add(per); } public void showAllStaffs() { for(Staff per:allStaffs) { System.out.println("Obj:"+per.toString()); } }}最后是Main.java
package com.example.singleton;public class Main { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Company cp = new Company(); //方法一 :餓漢模式 Staff ceo1 = CEO. getCeo(); Staff ceo2 = CEO. getCeo(); cp.addStaff(ceo1); cp.addStaff(ceo2); cp.showAllStaffs(); System. out .println("--------------------------------------" ); //測試發現有兩條數據是一樣的,說明單例類只能提供一個對象 Staff vp1= new VP(); Staff vp2= new VP(); Staff staff1= new Staff(); Staff staff2= new Staff(); Staff staff3= new Staff(); cp.addStaff(vp1); cp.addStaff(vp2); cp.addStaff(staff1); cp.addStaff(staff2); cp.addStaff(staff3); cp.showAllStaffs(); }}輸出結果:
Obj:com.example.singleton.CEO@1cacd5d4Obj:com.example.singleton.CEO@1cacd5d4--------------------------------------Obj:com.example.singleton.CEO@1cacd5d4Obj:com.example.singleton.CEO@1cacd5d4Obj:com.example.singleton.VP@170a6001Obj:com.example.singleton.VP@2a24ed78Obj:com.example.singleton.Staff@5e6276e5Obj:com.example.singleton.Staff@126be4ccObj:com.example.singleton.Staff@697a1686以上就是這個例子的全部內容,其中CEO.java就是一個單例類,因為之前說了一個公司就只有一個CEO。從代碼中可以看到,CEO類不能通過new關鍵字來構造對象,因為構造方法已經被私有化。只能通過CEO類對外開放的getCeo方法來獲取對象,而這個對象是在申明的時候就已經被初始化,這就保證的對象的唯一性。以上單例模式的寫法就是所謂的餓漢模式。 (2)懶漢模式 懶漢模式與餓漢模式的區別在于,懶漢模式的對象是在調用了getInstance方法的時候初始化的。懶漢模式的實現方式如下,將CEO類修改。
package com.example.singleton;public class CEO extends Staff { // private static final CEO mCeo=new CEO(); private static CEO mCeo; // 構造函數私有化(構造方法的私有化是單例模式的核心) private CEO() { } // /** // * 方法一 // * 餓漢單例模式 // * // * @author HP // * // */ // public static CEO getCeo() { // return mCeo; // } /** * 方法二 * 懶漢模式 * @return */ public static synchronized CEO getInstance() { if (mCeo != null) { mCeo = new CEO(); } return mCeo ; } @Override public void work() { // TODO Auto-generated method stub super .work(); // 管理 vp }}對應的修改Main.java
package com.example.singleton;public class Main { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Company cp = new Company();// //方法一 :餓漢模式// Staff ceo1 = CEO.getCeo();// Staff ceo2 = CEO.getCeo(); //方法二:懶漢模式 Staff ceo1 = CEO. getInstance(); Staff ceo2 = CEO. getInstance(); cp.addStaff(ceo1); cp.addStaff(ceo2); cp.showAllStaffs(); System. out .println("--------------------------------------" ); //測試發現有兩條數據是一樣的,說明單例類只能提供一個對象 Staff vp1= new VP(); Staff vp2= new VP(); Staff staff1= new Staff(); Staff staff2= new Staff(); Staff staff3= new Staff(); cp.addStaff(vp1); cp.addStaff(vp2); cp.addStaff(staff1); cp.addStaff(staff2); cp.addStaff(staff3); cp.showAllStaffs(); }}懶漢模式中的getInstance方法中添加了synchronized關鍵字,也就是說getInstance是一個同步方法,這就是在多線程情況下保證對象唯一性的手段。但是有一個問題,那就是即使mCeo已經被初始化,每次調用getInstance方法的時候還是會同步,這樣就造成了不必要的資源浪費。這也是懶漢模式的最大問題所在。
△ 懶漢模式的優缺點 懶漢模式的優點是單例只有使用的時候才會被初始化,在一定程度上節約了資源;缺點是第一次加載的時候反應稍慢,最大了問題是每次調用getInstance方法的時候都要同步,會造成不必要的開銷。
(3)DCL實現方式 這種方式既可以實現在需要的時候在初始化實例,又保證線程安全,并且在調用getInstance方法的時候不同不同,其實現如下:
package com.example.singleton;public class CEO extends Staff {// private static final CEO mCeo=new CEO(); private static CEO mCeo; // 構造函數私有化(構造方法的私有化是單例模式的核心) private CEO() { }// /**// * 方法一// * 餓漢單例模式// *// * @author HP// *// */// public static CEO getCeo() {// return mCeo;// }// /**// * 方法二// * 懶漢模式// * @return// */// public static synchronized CEO getInstance() {// if (mCeo == null) {// mCeo = new CEO();// }// return mCeo;// } /** * 方法三 * DCL */ public static CEO getInstance() { if (mCeo == null) { synchronized (CEO.class ) { if (mCeo == null) { mCeo =new CEO(); } } } return mCeo ; } @Override public void work() { // TODO Auto-generated method stub super .work(); // 管理 vp }}這不是一種被推薦使用的單例模式寫法 (4)靜態內部類實現單例模式 其實現如下:
package com.example.singleton;public class CEO extends Staff { // private static final CEO mCeo=new CEO(); // private static CEO mCeo; // 構造函數私有化(構造方法的私有化是單例模式的核心) private CEO() { } // /** // * 方法一 // * 餓漢單例模式 // * // * @author HP // * // */ // public static CEO getCeo() { // return mCeo; // } // /** // * 方法二 // * 懶漢模式 // * @return // */ // public static synchronized CEO getInstance() { // if (mCeo == null) { // mCeo = new CEO(); // } // return mCeo; // } // /** // * 方法三 // * DCL // */ // public static CEO getInstance() { // if (mCeo==null) { // synchronized (CEO.class) { // if (mCeo==null) { // mCeo=new CEO(); // } // } // } // return mCeo; // } /** * 方法四 靜態內部類單例模式(推薦使用的單例模式) * * @return */ public static CEO getInstance() { return SinletonCEO. mCeo; } private static class SinletonCEO { private static final CEO mCeo = new CEO(); } @Override public void work() { // TODO Auto-generated method stub super .work(); // 管理 vp }}當第一次加載CEO類的時候并不會初始化mCeo,只有在第一次調用CEO的getInstance方法的時候mCeo才會被初始化。因此,第一次調用getInstance方法會導致虛擬機加載SinletonCEO類,這種方式不僅能夠確保線程安全,同時也能夠保證單例對象的唯一性,同時也延遲了單例的實例化。所以這是一種推薦使用的單例實現方式。
(5)其他單例實現方式 其中還有兩種單例的實現方式,他們是枚舉單例和使用容器實現單例模式。這里就不再具體介紹了,想了解的同學可以翻閱《Android源碼設計模式解析與實戰》藝術。
6 總結 不管是哪種方式實現的單例模式,它們的核心就是構造函數的私有化,并且通過靜態方法來獲取一個唯一的實例,在這個過程中我們必須要保證線程安全、防止反序列化導致重生成實例等問題!
新聞熱點
疑難解答