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@1cacd5d4
Obj:com.example.singleton.CEO@1cacd5d4
--------------------------------------
Obj:com.example.singleton.CEO@1cacd5d4
Obj:com.example.singleton.CEO@1cacd5d4
Obj:com.example.singleton.VP@170a6001
Obj:com.example.singleton.VP@2a24ed78
Obj:com.example.singleton.Staff@5e6276e5
Obj:com.example.singleton.Staff@126be4cc
Obj: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 總結
不管是哪種方式實現的單例模式,它們的核心就是構造函數的私有化,并且通過靜態方法來獲取一個唯一的實例,在這個過程中我們必須要保證線程安全、防止反序列化導致重生成實例等問題!