編輯:關於Android編程
前段時間公司一些同事在討論單例模式(我是最渣的一個,都插不上嘴 T__T ),這個模式使用的頻率很高,也可能是很多人最熟悉的設計模式,當然單例模式也算是最簡單的設計模式之一吧,簡單歸簡單,但是在實際使用的時候也會有一些坑。
確保某一個類只有一個實例,而且自行實例化並向整個系統提供這個實例。
單例模式的使用很廣泛,比如:線程池(threadpool)、緩存(cache)、對話框、處理偏好設置、和注冊表(registry)的對象、日志對象,充當打印機、顯卡等設備的驅動程序的對象等,這些類的對象只能有一個實例,如果制造出多個實例,就會導致很多問題的產生,程序的行為異常,資源使用過量,或者不一致的結果等,所以單例模式最主要的特點:
類圖很簡單,Singleton 類有一個 static 的 instance對象,類型為 Singleton ,構造函數為 private,提供一個 getInstance() 的靜態函數,返回剛才的 instance 對象,在該函數中進行初始化操作。
單例模式的寫法很多,總結一下:
延遲初始化,一般很多人稱為懶漢法,寫法一目了然,在需要使用的時候去調用getInstance()函數去獲取Singleton的唯一靜態對象,如果為空,就會去做一個額外的初始化操作。
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if(instance == null)
instance = new Singleton();
return instance;
}
}
需要注意的是這種寫法在多線程操作中是不安全的,後果是可能會產生多個Singleton對象,比如兩個線程同時執行getInstance()函數時,然後同時執行到 new 操作時,最後很有可能會創建兩個不同的對象。
需要做到線程安全,就需要確保任意時刻只能有且僅有一個線程能夠執行new Singleton對象的操作,所以可以在getInstance()函數上加上 synchronized 關鍵字,類似於:
public static synchronized Singleton getInstance() {
if(singleton == null)
instance = new Singleton();
return instance;
}
但是套用《Head First》上的一句話,對於絕大部分不需要同步的情況來說,synchronized 會讓函數執行效率糟糕一百倍以上(Since synchronizing a method could in some extreme cases decrease performance by a factor of 100 or higher),所以就有了double-checked(雙重檢測)的方法:
public class Singleton {
private volatile static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if (instance == null){
synchronized (Singleton.class){
if (instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
我們假設兩個線程A,B同時執行到了getInstance()這個方法,第一個if判斷,兩個線程同時為true,進入if語句,裡面有個 synchronized 同步,所以之後有且僅有一個線程A會執行到 synchronized 語句內部,接著再次判斷instance是否為空,為空就去new Singleton對象並且賦值給instance,A線程退出 synchronized 語句,交出同步鎖,B線程進入 synchronized 語句內部,if判斷instance是否為空,防止創建不同的instance對象,這也是第二個if判斷的作用,B線程發現不為空,所以直接退出,所以最終A和B線程可以獲取到同一個Singleton對象,之後的線程調用getInstance()函數,都會因為Instance不為空而直接返回,不會受到 synchronized 的性能影響。
double-checked方法用到了volatile關鍵字,volatile關鍵字的作用需要仔細介紹一下,在C/C++中,volatile關鍵字的作用和java中是不一樣的,總結一下:
C/C++中的volatile關鍵字作用可見性“可見性”指的是在一個線程中對該變量的修改會馬上由工作內存(Work Memory)寫回主內存(Main Memory),所以會馬上反應在其它線程的讀取操作中。順便一提,工作內存和主內存可以近似理解為實際電腦中的高速緩存和主存,工作內存是線程獨享的,主存是線程共享的。不可優化性“不可優化”特性,volatile告訴編譯器,不要對我這個變量進行各種激進的優化,甚至將變量直接消除,保證程序員寫在代碼中的指令,一定會被執行。順序性”順序性”,能夠保證Volatile變量間的順序性,編譯器不會進行亂序優化。Volatile變量與非Volatile變量的順序,編譯器不保證順序,可能會進行亂序優化。同時,C/C++ Volatile關鍵詞,並不能用於構建happens-before語義,因此在進行多線程程序設計時,要小心使用volatile,不要掉入volatile變量的使用陷阱之中。java中volatile關鍵字作用Java也支持volatile關鍵字,但它被用於其他不同的用途。當volatile用於一個作用域時,Java保證如下:(適用於Java所有版本)讀和寫一個volatile變量有全局的排序。也就是說每個線程訪問一個volatile作用域時會在繼續執行之前讀取它的當前值,而不是(可能)使用一個緩存的值。(但是並不保證經常讀寫volatile作用域時讀和寫的相對順序,也就是說通常這並不是有用的線程構建)。(適用於Java5及其之後的版本)volatile的讀和寫建立了一個happens-before關系,類似於申請和釋放一個互斥鎖[8]。使用volatile會比使用鎖更快,但是在一些情況下它不能工作。volatile使用范圍在Java5中得到了擴展,特別是雙重檢查鎖定現在能夠正確工作[9]。上面有一個細節,java 5版本之後volatile的讀與寫才建立了一個happens-before的關系,之前的版本會出現一個問題:Why is volatile used in this example of double checked locking,這個答案寫的很清楚了,線程 A 在完全構造完 instance 對象之前就會給 instance 分配內存,線程B在看到 instance 已經分配了內存不為空就回去使用它,所以這就造成了B線程使用了部分初始化的 instance 對象,最後就會出問題了。Double-checked locking裡面有一句話
As of J2SE 5.0, this problem has been fixed. The volatile keyword now ensures that
multiple threads handle the singleton instance correctly. This new idiom is
described in [2] and [3].
所以對於 android 來說,使用 volatile關鍵字是一點問題都沒有的了。
“餓漢法”就是在使用該變量之前就將該變量進行初始化,這當然也就是線程安全的了,寫法也很簡單:
private static Singleton instance = new Singleton();
private Singleton(){
name = "eager initialization thread-safety 1";
}
public static Singleton getInstance(){
return instance;
}
或者
private static Singleton instance = null;
private Singleton(){
name = "eager initialization thread-safety 2";
}
static {
instance = new Singleton();
}
public Singleton getInstance(){
return instance;
}
代碼都很簡單,一個是直接進行初始化,另一個是使用靜態塊進行初始化,目的都是一個:在該類進行加載的時候就會初始化該對象,而不管是否需要該對象。這麼寫的好處是編寫簡單,而且是線程安全的,但是這時候初始化instance顯然沒有達到lazy loading的效果。
由於在java中,靜態內部類是在使用中初始化的,所以可以利用這個天生的延遲加載特性,去實現一個簡單,延遲加載,線程安全的單例模式:
private static class SingletonHolder{
private static final Singleton instance = new Singleton();
}
private Singleton(){
name = "static inner class thread-safety";
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
定義一個 SingletonHolder 的靜態內部類,在該類中定義一個外部類 Singleton 的靜態對象,並且直接初始化,在外部類 Singleton 的 getInstance() 方法中直接返回該對象。由於靜態內部類的使用是延遲加載機制,所以只有當線程調用到 getInstance() 方法時才會去加載 SingletonHolder 類,加載這個類的時候又會去初始化 instance 變量,所以這個就實現了延遲加載機制,同時也只會初始化這一次,所以也是線程安全的,寫法也很簡單。
上面提到的所有實現方式都有兩個共同的缺點:
都需要額外的工作(Serializable、transient、readResolve())來實現序列化,否則每次反序列化一個序列化的對象實例時都會創建一個新的實例。 可能會有人使用反射強行調用我們的私有構造器(如果要避免這種情況,可以修改構造器,讓它在創建第二個實例的時候拋異常)。JDK1.5 之後加入 enum 特性,可以使用 enum 來實現單例模式:
enum SingleEnum{
INSTANCE("enum singleton thread-safety");
private String name;
SingleEnum(String name){
this.name = name;
}
public String getName(){
return name;
}
}
使用枚舉除了線程安全和防止反射強行調用構造器之外,還提供了自動序列化機制,防止反序列化的時候創建新的對象。因此,Effective Java推薦盡可能地使用枚舉來實現單例。但是很不幸的是 android 中並不推薦使用 enum ,主要是因為在 java 中枚舉都是繼承自 java.lang.Enum 類,首次調用時,這個類會調用初始化方法來准備每個枚舉變量。每個枚舉項都會被聲明成一個靜態變量,並被賦值。在實際使用時會有點問題,這是 google 的官方文檔介紹:
Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android
登記式單例實際上維護了一組單例類的實例,將這些實例存放在一個Map(登記薄)中,對於已經登記過的實例,則從Map直接返回,對於沒有登記的,則先登記,然後返回。
//類似Spring裡面的方法,將類名注冊,下次從裡面直接獲取。
public class Singleton {
private static Map map = new HashMap();
static{
Singleton single = new Singleton();
map.put(single.getClass().getName(), single);
}
//保護的默認構造子
protected Singleton(){}
//靜態工廠方法,返還此類惟一的實例
public static Singleton getInstance(String name) {
if(name == null) {
name = Singleton.class.getName();
System.out.println("name == null"+"--->name="+name);
}
if(map.get(name) == null) {
try {
map.put(name, (Singleton) Class.forName(name).newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return map.get(name);
}
//一個示意性的商業方法
public String about() {
return "Hello, I am RegSingleton.";
}
public static void main(String[] args) {
Singleton single3 = Singleton.getInstance(null);
System.out.println(single3.about());
}
}
這種方式我極少見到,另外其實內部實現還是用的餓漢式單例,因為其中的static方法塊,它的單例在類被裝載的時候就被實例化了。
綜上所述,平時在 android 中使用 double-checked 或者 SingletonHolder 都是可以的,畢竟 android 早就不使用 JDK5 之前的版本了。由於 android 中的多進程機制,在不同進程中無法創建同一個 instance 變量,就像 Application 類會初始化兩次一樣,這點需要注意。
但是不管采取何種方案,請時刻牢記單例的三大要點:
https://github.com/zhaozepeng/Design-Patterns
前言 在H5火熱的時代,許多框架都出了底部彈窗的控件,在H5被稱為彈出菜單ActionSheet,今天我們也來模仿一個ios的底部彈窗,取材於蘋果QQ的選擇頭像功能正文
今天在android項目中使用AES對數據進行加解密,遇到了很多問題,網上也找了很多資料,也不行。不過最後還是讓我給搞出來了,這裡把這個記錄下來,不要讓別人走我的彎路,因
這篇文章主要介紹一下如何實現View的3D旋轉效果,實現的主要原理就是圍繞Y軸旋轉,同時在Z軸方面上有一個深入的縮放。演示的demo主要有以下幾個重點: 1,自定義旋轉動
在前面一篇文章淺談Service Manager成為Android進程間通信(IPC