引言
大浪淘沙,洗盡鉛華無數,Java語言綿延數十載,前人留下了許多的輪子,Android使用java語言,自然需要用到這些輪子。今天,本少年就從頭擼一把設計模式界的hello world——單例模式。
介紹
- 定義:在Android中,單例模式其實使用頻率還是很高的,顧名思義,所謂單例就是指單個實例,如果一個類使用了單例模式,那么這個類的實例在整個APP中就只有一個實例。
- 應用場景:比如有一個util幫助類,它提供了一些功能,但是比較耗費資源,像線程池、IO訪存、數據庫操作等等。這種就不必new 很多實例,浪費資源。
幾種寫法
PS:XML布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="st.zlei.com.androidsingleton.MainActivity"
android:orientation="vertical">
<Button
android:id="@+id/hungry_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="餓漢模式"
/>
<Button
android:id="@+id/lazy_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="基本懶漢模式" />
<Button
android:id="@+id/DCL_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="DCL單例模式" />
<Button
android:id="@+id/InnerClass_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="靜態內部類單例模式" />
<Button
android:id="@+id/map_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="容器單例模式" />
</LinearLayout>
1. 餓漢模式
上代碼:
單例類
Singleton_hungryMan 是單例的類。
public class Singleton_hungryMan {
private static final String TAG = "TestSingle";
private static Singleton_hungryMan singletonHungryMan = new Singleton_hungryMan();
//構造方法為private,其他地方就無法new本類的對象
private Singleton_hungryMan() {
}
//static以便類名.方法名調用
public static Singleton_hungryMan getInstance(){
return singletonHungryMan;
}
public void work(){
Log.d(TAG, "onClick: "+"我是餓漢模式里面的work方法");
}
}
調用Singleton_hungryMan
//1.test餓漢模式
hungry_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//實例1:
Singleton_hungryMan singletonHungryMan1 = Singleton_hungryMan.getInstance();
Log.d(TAG, "onClick: "+singletonHungryMan1.hashCode());
singletonHungryMan1.work();
//實例2:
Singleton_hungryMan singletonHungryMan2 = Singleton_hungryMan.getInstance();
Log.d(TAG, "onClick: "+singletonHungryMan2.hashCode());
singletonHungryMan2.work();
}
});
結果
描述:
Singleton_hungryMan的構造方法是private所以不能new,只能通過getInstance()獲取,而這個instance在聲明的時候就已經初始化了,因此,Singleton_hungryMan這個類的實例只有一個。從log的打印也可以看出來兩次得到的實例singletonHungryMan1 和singletonHungryMan12的哈希值是一樣的,說明這兩個對象指向的是同一個實例。
2. 基本的懶漢模式
上代碼:
單例類
Singleton_lazyMan 是單例的類。
public class Singleton_lazyMan {
private static final String TAG = "TestSingle";
//私有的構造方法
private Singleton_lazyMan() {
}
private static Singleton_lazyMan singletonLazyMan = null;
//synchronized保證多線程的同步問題
public static synchronized Singleton_lazyMan getInstance(){
if (singletonLazyMan == null){
singletonLazyMan = new Singleton_lazyMan();
}
return singletonLazyMan;
}
public void work(){
Log.d(TAG, "onClick: "+"我是基本的懶漢模式里面的work");
}
}
*調用Singleton_lazyMan *
//2.test基本的懶漢模式
lazy_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//實例1:
Singleton_lazyMan singletonLazyMan1 = Singleton_lazyMan.getInstance();
singletonLazyMan1.work();
Log.d(TAG, "onClick: "+singletonLazyMan1.hashCode());
//實例2:
Singleton_lazyMan singletonLazyMan2 = Singleton_lazyMan.getInstance();
singletonLazyMan2.work();
Log.d(TAG, "onClick: "+singletonLazyMan2.hashCode());
}
});
結果
描述:
同上面一樣,兩個實例的哈希值是一樣的,說明單例的效果是達到了的。
synchronized的作用是保證多線程情況下單例對象的唯一性,試想當一個線程運行到這個地方的時候停下來了,另一個線程拿到了cpu去運行,是不是就會new 了兩個Singleton_lazyMan()
這里寫圖片描述
還有一個問題,這種懶漢模式在每次調用時都會用synchronized同步,這樣子會造成不必要的同步開銷。基于此,接下來將介紹一種改良版的懶漢模式。
3. 改良版的懶漢模式(DCL)
上代碼:
單例類
Sington_DCL 是單例的類。
public class Sington_DCL {
private static final String TAG = "TestSingle";
private Sington_DCL() {
}
private static Sington_DCL singtonDcl = null;
public static Sington_DCL getInstance(){
if (singtonDcl == null){
synchronized (Sington_DCL.class){
if (singtonDcl == null){
singtonDcl = new Sington_DCL();
}
}
}
return singtonDcl;
}
public void work(){
Log.d(TAG, "onClick: "+"我是DCL模式里面的work");
}
}
*調用Sington_DCL *
//3.test DCL單例模式
DCL_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//實例1:
Sington_DCL singtonDcl1 = Sington_DCL.getInstance();
singtonDcl1.work();
Log.d(TAG, "onClick: "+singtonDcl1.hashCode());
//實例2:
Sington_DCL singtonDcl2 = Sington_DCL.getInstance();
singtonDcl2.work();
Log.d(TAG, "onClick: "+singtonDcl2.hashCode());
}
});
結果
描述:
從結果的圖片看到,單例的效果是達到了的。
跟上面的懶漢模式相比,只有在第一次調用getInntance()才會同步,之后調用,便不會走synchronized里面的內容了,跟上面的本質區別還是同步鎖加的位置不同導致的。
這里需要注意有兩個null的判斷,第一個if判斷還是很好理解的,就是說,如果singtonDcl不是null就不必再執行new的操作了。第二個if判斷比較復雜,簡單的理解就是說:
singtonDcl = new Sington_DCL();
這句話在java中雖然就是一句,但是在底層CPU執行的時候是拆分成好幾步的:
1.給實例分配內存 2.調用構造函數,初始化成員字段 3.將對象singtonDcl 指向剛剛分配的內存空間
問題在于這三步走,第一步是最先執行,但是第二步和第三步是亂序的,也就是說不確定2和3誰先誰后
那么問題來了,舉個栗子,線程A執行了new Sington_DCL();`,底層cpu執行到1,3,即將執行2的時候線程B拿到了cpu,因為A已經把3執行了,這時候singtonDcl已經有內存空間了,換句話說singtonDcl已經不是null了,那么B就直接拿走了singtonDcl ,但事實上2還沒有沒有執行,B拿走的singtonDcl并不能用。所以還有一層if判斷。
但是想想,這種情況發生的概率非常低的呀。
4. 靜態內部類單例模式
上代碼:
單例類:
public class Singleton_InnerClass {
private static final String TAG = "TestSingle";
private Singleton_InnerClass() {
}
public static Singleton_InnerClass getInstance(){
return SingletonHolder.mInstance;
}
private static class SingletonHolder {
//第一次調用mInstance會初始化
private static final Singleton_InnerClass mInstance = new Singleton_InnerClass();
}
public void work(){
Log.d(TAG, "onClick: "+"我是靜態內部類單例模式里面的work");
}
}
調用方式都一樣就不佳贅述了
5. 使用容器實現單例模式
上代碼:
單例類:
public class SIngleton_MapManager {
private static final String TAG = "TestSingle";
private static Map<String,Object> map = new HashMap<String,Object>();
private SIngleton_MapManager() {
}
public static void registService(String key,Object obj){
if (!map.containsKey(key)){
map.put(key,obj);
}
}
public static Object getService(String key){
if (map.containsKey(key)){
Object obj = map.get(key);
return obj;
}
else {
return null;
}
}
}
調用方式
//5.test map容器單例模式
map_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SIngleton_MapManager.registService("a","A");
SIngleton_MapManager.registService("b","B");
//實例1:
Object service1 = SIngleton_MapManager.getService("a");
Log.d(TAG, "onClick: "+service1+"-----"+service1.hashCode());
//實例2:
Object service2 = SIngleton_MapManager.getService("a");
Log.d(TAG, "onClick: "+service2+"-----"+service2.hashCode());
}
});
描述:
這種方式很熟悉有沒有??沒錯,在Android中拿到系統的一些manager比如getSystemService(。。。)就是這種方式,這樣子系統核心服務都以單例存在,減少了資源消耗。平時自己寫單例模式好像不怎么用得到?
6. 總結
單例模式減少內存,減輕性能開銷,使用非常廣泛,本文介紹的幾種單例的寫法,推薦使用改良版的懶漢模式和靜態內部類的單例模式這兩種,當然單例還有其他寫法,但是比較小眾,這里就不再介紹了。
本文所使用的代碼可以在本人的github上找到,點這里傳送門走起