這次要介紹1下對象池模式(Object Pool Pattern),這個模式為常見 23 種設計模式以外的設計模式,介紹的初衷主要是在平時的 android 開發中常常會看到,比如 ThreadPool 和 MessagePool 等。
在 java 中,所有對象的內存由虛擬機管理,所以在某些情況下,需要頻繁創建1些生命周期很短使用完以后就能夠立即燒毀,但是數量很大的對象集合,那末此時 GC 的次數必定會增加,這時候候為了減小系統 GC 的壓力,對象池模式就很適用了。對象池模式也是創建型模式之1,它不是根據使用動態的分配和燒毀內存,而是保護1個已初始化好若干對象的對象池以供使用。客戶端使用的時候從對象池中去申請1個對象,當該對象使用完以后,客戶端會返回給對象池,而不是立即燒毀它,這步操作可以手動或自動完成。
從 Java 語言的特性來分析1下,在 Java 中,對象的生命周期大致包括3個階段:對象的創建,對象的使用,對象的清除。因此,對象的生命周期長度可用以下的表達式表示:T = T1 + T2 +T3。其中T1表示對象的創建時間,T2 表示對象的使用時間,而 T3 則表示其清除時間。由此,我們可以看出,只有 T2 是真正有效的時間,而 T1、T3 則是對象本身的開消。下面再看看 T1、T3 在對象的全部生命周期中所占的比例。Java對象是通過構造函數來創建的,在這1進程中,該構造函數鏈中的所有構造函數也都會被自動調用。另外,默許情況下,調用類的構造函數時,Java 會把變量初始化成肯定的值:所有的對象被設置成 null,整數變量(byte、short、int、long)設置成 0, float 和 double 變量設置成 0.0,邏輯值設置成false。所以用new關鍵字來新建1個對象的時間開消是很大的,以下表所示:
運算操作 | 示例 | 標準化時間 |
---|---|---|
本地賦值 | i = n | 1.0 |
實例賦值 | this.i = n | 1.2 |
方法調用 | Funct() | 5.9 |
新建對象 | New Object() | 980 |
新建數組 | New int[10] | 3100 |
從表中可以看出,新建1個對象需要980個單位的時間,是本地賦值時間的980倍,是方法調用時間的166倍,而若新建1個數組所花費的時間就更多了。
再看清除對象的進程,我們知道,Java 語言的1個優勢,就是 Java 程序員勿需再像 C/C++ 程序員那樣,顯式地釋放對象,而由稱為垃圾搜集器(Garbage Collector)的自動內存管理系統,定時或在內存凸現出不足時,自動回收垃圾對象所占的內存。凡事有益總也有弊,這雖然為 Java 程序設計者提供了極大的方便,但同時它也帶來了較大的性能開消。這類開消包括兩方面,首先是對象管理開消,GC為了能夠正確釋放對象,它必須監控每個對象的運行狀態,包括對象的申請、援用、被援用、賦值等。其次,在 GC 開始回收“垃圾”對象時,系統會暫停利用程序的履行,而獨自占用 CPU。
因此,如果要改良利用程序的性能,1方面應盡可能減少創建新對象的次數;同時,還應盡可能減少 T1、T3 的時間,而這些都可以通過對象池技術來實現。所以對象池主要是用來提升性能,在某些情況下,對象池對性能有極大的幫助。但是還有1點需要注意,對象池會增加對象生命周期的復雜度,這是由于從對象池獲得的對象和返還給對象池的對象都沒有真實的創建或燒毀。
PS:對技術感興趣的同鞋加群5446459721起交換。
java/android 設計模式學習筆記目錄
從上面的介紹可以總結:對象池模式適用于“需要使用到大量的同1類對象,這些對象的初始化會消耗大量的系統資源,而且它們只需要使用很短的時間,這類操作會對系統的性能有1定影響”的情況,總結1下就是在下面兩種分配模式下可以選擇使用對象池:
1般對象池模式的 uml 類圖如圖所示,他有3個角色
我們先以 android 源碼中的 MessagePool 為例子來分析1下 ObjectPool 在實際開發中的使用效果,以后寫1個小的 demo 。
在 android 中使用 new Message() , Message.obtain() 和 Handler.obtainMessage() 都能夠取得1個 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);
}
1樣是調用到了 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.
* <p>
* 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.
* </p>
*/
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++;
}
}
}
從這幾處代碼可以很清楚的看到:
我們參照上面的 uml 類圖寫1個 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<String> h;
public ArrayList<String> i;
public ArrayList<String> j;
public ArrayList<String> k;
public ArrayList<String> l;
public Reusable(){
h = new ArrayList<>();
i = new ArrayList<>();
j = new ArrayList<>();
k = new ArrayList<>();
l = new ArrayList<>();
}
}
然后用1個 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<Reusable> available = new ArrayList<>();
private static List<Reusable> 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;
}
}
需要說明的是:
對象池模式從上面來分析可以說是用途很大,但是這個模式1定要注意使用的場景,也就是最上面提到的兩點:“對象以固定的速度不斷地分配,垃圾搜集時間逐漸增加,內存使用率隨之增大”和“對象分配存在爆發期,而每次爆發都會致使系統遲滯,并伴隨明顯的GC中斷”,其他的1般情況下最好不要使用對象池模式,我們后面還會提到它的缺點和很多人對其的批評。
優點上面就已論述的很清楚了,對那些”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 之類的對象。
上面已提過了,使用對象池模式時,每次進行回收操作前都需要將該對象的相干成員變量重置,如果不重置將會致使下1次重復使用該對象時候出現預感不到的毛病,同時的,如果回收對象的成員變量很大,不重置還可能會出現內存 OOM 和信息泄漏等問題。另外的,對象池模式絕大多數情況下都是在多線程中訪問的,所以做好同步工作也是極為重要的。原文:https://en.wikipedia.org/wiki/Object_pool_pattern#Pitfalls
除以上兩點以外,還需要注意以下問題:
1些開發者不建議在某些語言例如 Java 中使用對象池模式,特別是對象只使用內存并且不會持有其他資源的語言。這些反對者持有的觀點是 new 的操作只需要 10 條指令,而使用對象池模式則需要成百上千條指令,明顯增加了復雜度,而且 GC 操作只會去掃描“活著”的對象援用所指向的內存,而不是它們的成員變量所使用的那塊內存,這就意味著,任何沒有援用的“死亡”對象都能夠被 GC 以很小的代價跳過,相反如果使用對象池模式,持有大量“活著“的對象反而會增加 GC 的時間。原文:https://en.wikipedia.org/wiki/Object_pool_pattern#Criticism
https://github.com/zhaozepeng/Design-Patterns/tree/master/ObjectPoolPattern
http://www.cnblogs.com/xinye/p/3907642.html
https://en.wikipedia.org/wiki/Object_pool_pattern
http://blog.csdn.net/liyangbing315/article/details/4942870
http://www.infoq.com/cn/news/2015/07/ClojureWerkz
http://blog.csdn.net/xplee0576/article/details/46875555