編輯:關於Android編程
這次要介紹一下對象池模式(Object Pool Pattern),這個模式為常見 23 種設計模式之外的設計模式,介紹的初衷主要是在平時的 android 開發中經常會看到,比如 ThreadPool 和 MessagePool 等。
在 java 中,所有對象的內存由虛擬機管理,所以在某些情況下,需要頻繁創建一些生命周期很短使用完之後就可以立即銷毀,但是數量很大的對象集合,那麼此時 GC 的次數必然會增加,這時候為了減小系統 GC 的壓力,對象池模式就很適用了。對象池模式也是創建型模式之一,它不是根據使用動態的分配和銷毀內存,而是維護一個已經初始化好若干對象的對象池以供使用。客戶端使用的時候從對象池中去申請一個對象,當該對象使用完之後,客戶端會返回給對象池,而不是立即銷毀它,這步操作可以手動或者自動完成。
從 Java 語言的特性來分析一下,在 Java 中,對象的生命周期大致包括三個階段:對象的創建,對象的使用,對象的清除。因此,對象的生命周期長度可用如下的表達式表示:T = T1 + T2 +T3。其中T1表示對象的創建時間,T2 表示對象的使用時間,而 T3 則表示其清除時間。由此,我們可以看出,只有 T2 是真正有效的時間,而 T1、T3 則是對象本身的開銷。下面再看看 T1、T3 在對象的整個生命周期中所占的比例。Java對象是通過構造函數來創建的,在這一過程中,該構造函數鏈中的所有構造函數也都會被自動調用。另外,默認情況下,調用類的構造函數時,Java 會把變量初始化成確定的值:所有的對象被設置成 null,整數變量(byte、short、int、long)設置成 0, float 和 double 變量設置成 0.0,邏輯值設置成false。所以用new關鍵字來新建一個對象的時間開銷是很大的,如下表所示:
從表中可以看出,新建一個對象需要980個單位的時間,是本地賦值時間的980倍,是方法調用時間的166倍,而若新建一個數組所花費的時間就更多了。
再看清除對象的過程,我們知道,Java 語言的一個優勢,就是 Java 程序員勿需再像 C/C++ 程序員那樣,顯式地釋放對象,而由稱為垃圾收集器(Garbage Collector)的自動內存管理系統,定時或在內存凸現出不足時,自動回收垃圾對象所占的內存。凡事有利總也有弊,這雖然為 Java 程序設計者提供了極大的方便,但同時它也帶來了較大的性能開銷。這種開銷包括兩方面,首先是對象管理開銷,GC為了能夠正確釋放對象,它必須監控每一個對象的運行狀態,包括對象的申請、引用、被引用、賦值等。其次,在 GC 開始回收“垃圾”對象時,系統會暫停應用程序的執行,而獨自占用 CPU。
因此,如果要改善應用程序的性能,一方面應盡量減少創建新對象的次數;同時,還應盡量減少 T1、T3 的時間,而這些均可以通過對象池技術來實現。所以對象池主要是用來提升性能,在某些情況下,對象池對性能有極大的幫助。但是還有一點需要注意,對象池會增加對象生命周期的復雜度,這是因為從對象池獲取的對象和返還給對象池的對象都沒有真正的創建或者銷毀。
從上面的介紹可以總結:對象池模式適用於“需要使用到大量的同一類對象,這些對象的初始化會消耗大量的系統資源,而且它們只需要使用很短的時間,這種操作會對系統的性能有一定影響”的情況,總結一下就是在下面兩種分配模式下可以選擇使用對象池:
對象以固定的速度不斷地分配,垃圾收集時間逐步增加,內存使用率隨之增大;對象分配存在爆發期,而每次爆發都會導致系統遲滯,並伴有明顯的GC中斷。在絕大多數情況下,這些對象要麼是數據容器,要麼是數據封裝器,其作用是在應用程序和內部消息總線、通信層或某些API之間充當一個信封。這很常見。例如,數據庫驅動會針對每個請求和響應創建 Request 和 Response 對象,消息系統會使用 Message 和 Event 封裝器,等等。對象池可以幫助保存和重用這些構造好的對象實例。
vcfJqzwvcD4NClJldXNhYmxlo7q21M/zwOCjrLjDttTP89TayrW8ysq508PW0NDo0qrGtbextcS0tL2ous3P+rvZo6yyosfSs/XKvLuvstnX97vhuty63M/7usTKsbzkus3Q1MTco7tDbGllbnSjusq508MgUmV1c2FibGUgwOC21M/ztcS9x8mro7tSZXVzYWJsZVBvb2yjurncwO0gUmV1c2FibGUgttTP88DgtcTL+dPQttTP86OsuakgQ2xpZW50IL3HyavKudPDoaOhoaGhzaizo8fpv/bPwqOsztLDx8+jzfvL+dPQtcQgUmV1c2FibGUgttTP87a8u+GxuyBSZXN1YWJsZVBvb2wgudzA7aOsy/nS1L/J0tTKudPDPGEgaHJlZj0="http://blog.csdn.net/self_study/article/details/50835410">單例模式構建 ReusablePool,當然其他的工廠方法模式也是可以的 。Client 調用 getInstance 方法獲取到 ReusablePool 的對象,接著調用 acquireReusable 方法去獲取 Reusable 對象,使用完之後調用 releaseReusable 方法傳入該對象重新交給 ReusablePool 管理。
我們先以 android 源碼中的 MessagePool 為例子來分析一下 ObjectPool 在實際開發中的使用效果,之後寫一個小的 demo 。
在 android 中使用 new Message() , Message.obtain() 和 Handler.obtainMessage() 都能夠獲得一個 Message 對象,但是為什麼後兩個的效率會高於前者呢?先來看看源碼的 Message.obtain() 函數:
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
返回的是 sPool 這個對象,繼續看看 Handler.obtainMessage() 函數:
/**
* Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
* creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
* If you don't want that facility, just call Message.obtain() instead.
*/
public final Message obtainMessage()
{
return Message.obtain(this);
}
一樣是調用到了 Message.obtain() 函數,那麼我們就從這個函數開始分析, Message 類是如何構建 MessagePool 這個角色的呢?看看這幾處的代碼:
//變量的構建
private static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;
private static boolean gCheckRecycle = true;
Message 對象的回收
/**
* Return a Message instance to the global pool.
*
* You MUST NOT touch the Message after calling this function because it has * effectively been freed. It is an error to recycle a message that is currently * enqueued or that is in the process of being delivered to a Handler. *
*/ public void recycle() { if (isInUse()) { if (gCheckRecycle) { throw new IllegalStateException("This message cannot be recycled because it " + "is still in use."); } return; } recycleUnchecked(); } /** * Recycles a Message that may be in-use. * Used internally by the MessageQueue and Looper when disposing of queued Messages. */ void recycleUnchecked() { // Mark the message as in use while it remains in the recycled object pool. // Clear out all other details. flags = FLAG_IN_USE; what = 0; arg1 = 0; arg2 = 0; obj = null; replyTo = null; sendingUid = -1; when = 0; target = null; callback = null; data = null; synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++; } } }
從這幾處代碼可以很清楚的看到:
sPool 這個靜態對象實際上是相當於構建了一個長度為 MAX_POOL_SIZE 的鏈表, recycleUnchecked 方法以內置鎖的方式(線程安全),判斷當前對象池的大小是否小於50,若小於50,則會被加到鏈表的頭部,並把 next 對象指向上一次鏈表的頭部;若大於等於50,則直接丟棄掉,那麼這些被丟棄的Message將交由GC處理。recycleUnchecked 方法將待回收的Message對象字段先置空,以便節省 MessagePool 的內存和方便下一次的直接使用;源碼這麼一看, android 中 MessagePool 其實實現方式還是很簡單的。
我們參照上面的 uml 類圖寫一個 demo,首先要定義 Reusable 這個角色,為了直觀,我們將該類定義了很多成員變量,用來模擬 expensive initialization:
Reusable.class
public class Reusable {
public String a;
public String b;
public String c;
public String d;
public String e;
public String f;
public String g;
public ArrayList h;
public ArrayList i;
public ArrayList j;
public ArrayList k;
public ArrayList l;
public Reusable(){
h = new ArrayList<>();
i = new ArrayList<>();
j = new ArrayList<>();
k = new ArrayList<>();
l = new ArrayList<>();
}
}
然後用一個 IReusablePool.class接口來定義對象池的基本行為:
public interface IReusablePool {
Reusable requireReusable();
void releaseReusable(Reusable reusable);
void setMaxPoolSize(int size);
}
最後實現該接口:
public class ReusablePool implements IReusablePool {
private static final String TAG = "ReusablePool";
private static volatile ReusablePool instance = null;
private static List available = new ArrayList<>();
private static List inUse = new ArrayList<>();
private static final byte[] lock = new byte[]{};
private static int maxSize = 5;
private int currentSize = 0;
private ReusablePool() {
available = new ArrayList<>();
inUse = new ArrayList<>();
}
public static ReusablePool getInstance() {
if (instance == null) {
synchronized (ReusablePool.class) {
if (instance == null) {
instance = new ReusablePool();
}
}
}
return instance;
}
@Override
public Reusable requireReusable() {
synchronized (lock) {
if (currentSize >= maxSize) {
throw new RuntimeException("pool has gotten its maximum size");
}
if (available.size() > 0) {
Reusable reusable = available.get(0);
available.remove(0);
currentSize++;
inUse.add(reusable);
return reusable;
} else {
Reusable reusable = new Reusable();
inUse.add(reusable);
currentSize++;
return reusable;
}
}
}
@Override
public void releaseReusable(Reusable reusable) {
if (reusable != null) {
reusable.a = null;
reusable.b = null;
reusable.c = null;
reusable.d = null;
reusable.e = null;
reusable.f = null;
reusable.g = null;
reusable.h.clear();
reusable.i.clear();
reusable.j.clear();
reusable.k.clear();
reusable.l.clear();
}
synchronized (lock) {
inUse.remove(reusable);
available.add(reusable);
currentSize--;
}
}
@Override
public void setMaxPoolSize(int size) {
synchronized (lock) {
maxSize = size;
}
}
}
最後測試程序:
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_get:
new Thread(new Runnable() {
@Override
public void run() {
try {
Reusable reusable = ReusablePool.getInstance().requireReusable();
Log.e("CORRECT", "get a Reusable object " + reusable);
Thread.sleep(5000);
ReusablePool.getInstance().releaseReusable(reusable);
}catch (Exception e){
Log.e("ERROR", e.getMessage());
}
}
}).start();
break;
}
}
需要說明的是:
releaseReusable 方法不要忘記將 Reusable 對象中的成員變量重置,要不然下次使用會有問題;模擬了 5s 的延時,在 demo 中如果達到了 maxPoolSize ,繼續操作是拋出 RunTimeException ,這個在實際使用該過程中可以按照情況更改(可以拋出異常,新建一個對象返回或者直接在多線程模式下阻塞,直到有新對象,參考鏈接:
對象池模式從上面來分析可以說是用處很大,但是這個模式一定要注意使用的場景,也就是最上面提到的兩點:“對象以固定的速度不斷地分配,垃圾收集時間逐步增加,內存使用率隨之增大”和“對象分配存在爆發期,而每次爆發都會導致系統遲滯,並伴有明顯的GC中斷”,其他的一般情況下最好不要使用對象池模式,我們後面還會提到它的缺點和很多人對其的批判。
優點上面就已經闡述的很清楚了,對於那些”the rate of instantiation and destruction of a class is high” 和 “the cost of initializing a class instance is high”的場景優化是及其明顯的,例如 database connections,socket connections,threads 和類似於 fonts 和 Bitmap 之類的對象。
上面已經提過了,使用對象池模式時,每次進行回收操作前都需要將該對象的相關成員變量重置,如果不重置將會導致下一次重復使用該對象時候出現預料不到的錯誤,同時的,如果回收對象的成員變量很大,不重置還可能會出現內存 OOM 和信息洩露等問題。另外的,對象池模式絕大多數情況下都是在多線程中訪問的,所以做好同步工作也是極其重要的。原文:https://en.wikipedia.org/wiki/Object_pool_pattern
除了以上兩點之外,還需要注意以下問題:
一些開發者不建議在某些語言例如 Java 中使用對象池模式,特別是對象只使用內存並且不會持有其他資源的語言。這些反對者持有的觀點是 new 的操作只需要 10 條指令,而使用對象池模式則需要成百上千條指令,明顯增加了復雜度,而且 GC 操作只會去掃描“活著”的對象引用所指向的內存,而不是它們的成員變量所使用的那塊內存,這就意味著,任何沒有引用的“死亡”對象都能夠被 GC 以很小的代價跳過,相反如果使用對象池模式,持有大量“活著“的對象反而會增加 GC 的時間。原文:https://en.wikipedia.org/wiki/Object_pool_pattern
https://github.com/zhaozepeng/Design-Patterns/tree/master/ObjectPoolPattern
小米電視2s定價在2999很大程度上是小米電視2s功能的刪減,其中大家最為關注的是砍掉了3D功能,3d功能可能不是每個人都需要,但是有總比沒有要好嗎?你說對
很多時候我們開發的軟件需要向用戶提供軟件參數設置功能,例如我們常用的QQ,用戶可以設置是否允許陌生人添加自己為好友。對於軟件配置參數的保存,如果是window軟件通常我們
在網易數據酷的中看到一幅圖,非常有特色,因為最近用Canvas繪了不少圖表,就想用代碼把這幅圖也繪出來。 基本也繪出來了,效果圖如下: 繪制這張圖,a
關於判斷手機是否已經root的方法。如果app有一些特殊功能需要root權限,則需要判斷是否root。比如一些市場下載完app後自動安裝。方法1:/***