我們經(jīng)常會遇到使用對象復(fù)用的場景,比如線程池病毡,數(shù)據(jù)庫連接池啦膜,Android的消息機(jī)制里面的Message的通過靜態(tài)方法obtain()
獲取消息,EventBus
在的解析監(jiān)聽事件的訂閱者對象里的標(biāo)有@subscribe
的方法而引入的SubscriberMethodFinder.FindState對象池FIND_STATE_POOL
,Glide
在請求加載圖像的時候EngineJob內(nèi)部的Pools.Pool<EngineJob<?>> pool
和解碼DecodeJob內(nèi)部的Pools.Pool<DecodeJob<?>> pool
僧家。因為對象創(chuàng)建的開銷過大八拱,為了避免每一次用到某個對象的時候都去new
一個新的對象涯塔。對象池提供了這樣一種機(jī)制爹谭,當(dāng)我們需要某個對象的時候诺凡,我們希望先嘗試從對象池(相當(dāng)于緩存)中獲取,如果有就直接返回嘶卧,此時對象池應(yīng)該空一個位子出來(即對象池的元素個數(shù)減一),沒有則新創(chuàng)建一個對象返回穷娱,當(dāng)對象用完以后绑蔫,如果對象池還有空余位置則存放入其中同時對象池中元素的個數(shù)+1。
如何實現(xiàn)一個對象池呢泵额?軟件開發(fā)中經(jīng)常說的一句話是面向接口而不是面向?qū)崿F(xiàn)編程配深,因此將對象池抽象成一個接口。由于接口是功能的集合嫁盲,對象池應(yīng)該對外提供哪些功能呢篓叶?既然是對象池,那么它必然有一個獲取對象和回收對象的方法羞秤。由于我們希望它是一個通用型的接口缸托,對任意的類型都適用,任意類型都能實現(xiàn)獲取和回收,因此需要用到泛型棍苹,我們用Pool
來表示對象池,用acquire
來表示獲取對象,用recycle
來表示回收對象,于是就有可以得到下面這個接口皮迟。
public interface Pool<T> {
//從對象池中獲取對象
T acquire();
//將不需要的對象放回對象池,返回值boolen類型表示是否回收成功
boolean recycle(T t);
}
有了上面的接口,我們就來嘗試何實現(xiàn)一個簡單的對象池辨图。既然是對象池吆豹,必然容量是有限的,同時必須大于0晌砾,如何存放對象呢都伪,最簡單的方式是用數(shù)組。我們獲取對象的時候就查詢一下這個數(shù)組湿刽,看看其中有沒有可用的元素铃芦,有就返回此元素同時將該處位原來的元素置空耸弄,不然會出現(xiàn)內(nèi)存泄漏,沒有就返回null
∥辏回收對象的時候先遍歷一下數(shù)組是不是已經(jīng)填滿了贪薪,沒有填滿囱怕,就放入數(shù)組中,于是有下面的簡單實現(xiàn)。
public class SimplePool<T> implements Pool<T> {
private final Object[] mElements;
private final int mMaxPoolSize;
public SimplePool(int maxPoolSize) {
if (maxPoolSize <= 0) {
throw new IllegalArgumentException("The max pool size must be > 0");
}
mMaxPoolSize = maxPoolSize;
mElements = new Object[maxPoolSize];
}
@Override
public T acquire() {
for (int i = 0; i < mMaxPoolSize; i++) {
if (mElements[i] != null) {
T ele = (T) mElements[i];
mElements[i] = null;//置空侠坎,避免內(nèi)存泄漏
return ele;
}
}
return null;
}
@Override
public boolean recycle(T t) {
for (int i = 0; i < mMaxPoolSize; i++) {
if (mElements[i] == null) {
mElements[i] = t;
return true;
}
}
return false;
}
}
有了這個簡單的設(shè)計之后闷煤,就可以使用了近顷,一般都是將這個類簡單的設(shè)計在某個工具類中使用,我們不妨定義一個工具類叫PoolUtils
,定義要復(fù)用的對象類型為HeavyObject
,如下所示:
class HeavyObject {
public HeavyObject (){
//開銷大的操作
。。盐须。女坑。
}
}
class PoolUtils {
private static final Pool<HeavyObject> sPool = new SimplePool<>(10);
//獲取對象
public static HeavyObject obtain() {
HeavyObject obj = sPool.acquire();
return obj != null ? obj : new HeavyObject(); //沒有就創(chuàng)建新的
}
//回收對象
public static boolean recycle(HeavyObject object) {
return sPool.recycle(object);
}
}
在回頭看看我們實現(xiàn)的SimplePool
設(shè)計得如何呢?emmm,我們發(fā)現(xiàn)aquire
和recycle
方法總會從從頭到尾到數(shù)組的遍歷。假如我們的緩存數(shù)組mElements
的大小為n
,開始時數(shù)組里面其實并沒有元素,此時aquire
方法的時間復(fù)雜度讀為O(n)
(并且還沒有獲取到元素),recycle
方法的時間復(fù)雜度為O(1)
(放在下標(biāo)為0的位置),而當(dāng)數(shù)組mElements
中填滿元素后叮叹,aquire
方法的時間復(fù)雜度讀為O(1)
(返回下標(biāo)為0處的元素)悼粮,recycle
方法的時間復(fù)雜度為O(n)
(遍歷完數(shù)組后仍然不能存放)。我們希望aquire
和recycle
方法無論在mElements
是否有數(shù)據(jù)的情況下都能做到時間復(fù)雜度為O(1)
臭杰,那么如何做到這一點呢磁奖?
不難通過分析發(fā)現(xiàn)蜜托,aquire
和recycle
方法只要記住上次存取的位置就行了,我們將元素連續(xù)存放,比如上次是在下標(biāo)為i
處存放了元素(下標(biāo)從0~i
都有數(shù)據(jù)),那么aquire
則從i
處獲取,recycle
方法則在i+1
處存放充蓝,只要i+1<n
.實際上i+1
即為緩存池里面已經(jīng)存放的元素的個數(shù)卑笨,于是我們引入一個變量mPoolSize
用于記錄緩存池里面已經(jīng)存放的元素個數(shù)。對SimplePool
做一次修改。
public class SimplePool<T> implements Pool<T> {
private final Object[] mElements;
private final int mMaxPoolSize;
private int mPoolSize;
public SimplePool(int maxPoolSize) {
if (maxPoolSize <= 0) {
throw new IllegalArgumentException("The max pool size must be > 0");
}
mMaxPoolSize = maxPoolSize;
mElements = new Object[maxPoolSize];
}
@Override
public T acquire() {
if (mPoolSize > 0) {
int lastPooledIndex = mPoolSize - 1;
T t= (T) mElements[lastPooledIndex];
mElements[lastPooledIndex] = null;//置空,避免內(nèi)存泄漏
mPoolSize--;
return t;
}
return null;
}
@Override
public boolean recycle(T t) {
if (mPoolSize < mMaxPoolSize) {
mElements[mPoolSize] = t;
mPoolSize++;
return true;
}
return false;
}
}
通過重構(gòu)袁滥,現(xiàn)在的acquire
和recycle
方法只需要做一次判斷塑荒,而不用從頭開始遍歷數(shù)組,因此時間復(fù)雜度都成了O(1)
.
看起來一切都很完美了他托,但是事情好像并沒有那么簡單纫溃,在多線程的情況下,比如某一時刻A線程正在執(zhí)行aquire
操作,B線程正在執(zhí)行recycle
操作很容易出現(xiàn)很容出現(xiàn)數(shù)據(jù)不一致的的安全性問題小染÷梢蹋看來我們得考慮一下線程安全的問題了。
為了能給用戶提供一個分別線程安全和線程不安全的對象池富蓄,我們將線程安全的對象池的實現(xiàn)命名為SynchronizedPool
,并給出實現(xiàn)
public class SynchronizedPool<T> implements Pool<T> {
private final Object mLock = new Object();
private final Object[] mElements;
private final int mMaxPoolSize;
private int mPoolSize;
public SynchronizedPool(int maxPoolSize) {
if (maxPoolSize <= 0) {
throw new IllegalArgumentException("The max pool size must be > 0");
}
mMaxPoolSize = maxPoolSize;
mElements = new Object[maxPoolSize];
}
@Override
public T acquire() {
synchronized (mLock) {
if (mPoolSize > 0) {
final int lastPooledIndex = mPoolSize - 1;
T instance = (T) mElements[lastPooledIndex];
mElements[lastPooledIndex] = null;//置空娇斑,避免內(nèi)存泄漏
mPoolSize--;
return instance;
}
return null;
}
}
@Override
public boolean recycle(T t) {
synchronized (mLock) {
if (mPoolSize < mMaxPoolSize) {
mElements[mPoolSize] = t;
mPoolSize++;
return true;
}
return false;
}
}
}
于是我們能夠提供了一個用戶自己選擇的對象池創(chuàng)建工廠方法,,讓用戶自定義是需要線程安全的還是線程不安全的版本:
class PoolFactory {
public static <T> Pool<T> createPool(boolean threadSafe, int maxPoolSize) {
return threadSafe ? new SynchronizedPool<T>(maxPoolSize) : new SimplePool<T>(maxPoolSize);
}
}
看到到這里讀者會問姻僧,這和標(biāo)題有什么關(guān)系?實際上androidX已經(jīng)提供了一個內(nèi)置的對象復(fù)用池遵倦,如下所示
package androidx.core.util;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* Helper class for creating pools of objects. An example use looks like this:
* <pre>
* public class MyPooledClass {
*
* private static final SynchronizedPool<MyPooledClass> sPool =
* new SynchronizedPool<MyPooledClass>(10);
*
* public static MyPooledClass obtain() {
* MyPooledClass instance = sPool.acquire();
* return (instance != null) ? instance : new MyPooledClass();
* }
*
* public void recycle() {
* // Clear state if needed.
* sPool.release(this);
* }
*
* . . .
* }
* </pre>
*
*/
public final class Pools {
/**
* Interface for managing a pool of objects.
*
* @param <T> The pooled type.
*/
public interface Pool<T> {
/**
* @return An instance from the pool if such, null otherwise.
*/
@Nullable
T acquire();
/**
* Release an instance to the pool.
*
* @param instance The instance to release.
* @return Whether the instance was put in the pool.
*
* @throws IllegalStateException If the instance is already in the pool.
*/
boolean release(@NonNull T instance);
}
private Pools() {
/* do nothing - hiding constructor */
}
/**
* Simple (non-synchronized) pool of objects.
*
* @param <T> The pooled type.
*/
public static class SimplePool<T> implements Pool<T> {
private final Object[] mPool;
private int mPoolSize;
/**
* Creates a new instance.
*
* @param maxPoolSize The max pool size.
*
* @throws IllegalArgumentException If the max pool size is less than zero.
*/
public SimplePool(int maxPoolSize) {
if (maxPoolSize <= 0) {
throw new IllegalArgumentException("The max pool size must be > 0");
}
mPool = new Object[maxPoolSize];
}
@Override
@SuppressWarnings("unchecked")
public T acquire() {
if (mPoolSize > 0) {
final int lastPooledIndex = mPoolSize - 1;
T instance = (T) mPool[lastPooledIndex];
mPool[lastPooledIndex] = null;//置空,避免內(nèi)存泄漏
mPoolSize--;
return instance;
}
return null;
}
@Override
public boolean release(@NonNull T instance) {
if (isInPool(instance)) {
throw new IllegalStateException("Already in the pool!");
}
if (mPoolSize < mPool.length) {
mPool[mPoolSize] = instance;
mPoolSize++;
return true;
}
return false;
}
private boolean isInPool(@NonNull T instance) {
for (int i = 0; i < mPoolSize; i++) {
if (mPool[i] == instance) {
return true;
}
}
return false;
}
}
/**
* Synchronized) pool of objects.
*
* @param <T> The pooled type.
*/
public static class SynchronizedPool<T> extends SimplePool<T> {
private final Object mLock = new Object();
/**
* Creates a new instance.
*
* @param maxPoolSize The max pool size.
*
* @throws IllegalArgumentException If the max pool size is less than zero.
*/
public SynchronizedPool(int maxPoolSize) {
super(maxPoolSize);
}
@Override
public T acquire() {
synchronized (mLock) {
return super.acquire();
}
}
@Override
public boolean release(@NonNull T element) {
synchronized (mLock) {
return super.release(element);
}
}
}
}
通過對比我們的設(shè)計和androidX的設(shè)計,發(fā)現(xiàn)androidX的SimplePool多了一個isInPool
方法,也就是校驗是否復(fù)用池里面已經(jīng)有相同的對象了雀瓢,如果存在就沒有必要再放入一個元素,不然也就失去了復(fù)用的意義。同時SynchronizedPool的方法的實現(xiàn)是繼承了SimplePool垦垂,只是在復(fù)寫acquire
和release
方法的時候加了一個鎖胁附,這樣代碼的復(fù)用率更高了。
如果讀者有看過Glide
源碼的話他匪,會發(fā)現(xiàn)Glide
我們前面的PoolFactory
上面更進(jìn)了一步,它提供了將工廠模式和對象池復(fù)用的精妙操作杯缺,一起來學(xué)習(xí)一下包雀。
定義一個工廠接口Factory
/**
* Creates new instances of the given type.
* 創(chuàng)建指定類型的新的實例
*
* @param <T> The type of Object that will be created.
*/
public interface Factory<T> {
T create();
}
定義了一個對象被放回對象池時可能進(jìn)行重置狀態(tài)操作的接口Resetter
/**
* Resets state when objects are returned to the pool.
* 當(dāng)對象放回復(fù)用池時重置狀態(tài)
*
* @param <T> The type of Object that will be reset.
*/
public interface Resetter<T> {
void reset(@NonNull T object);
}
定義了處理回收標(biāo)記的抽象類StateVerifier
public abstract class StateVerifier {
/**
* Throws an exception if we believe our object is recycled and inactive (i.e. is currently in an
* object pool).
* 如果我們確信我們的對象已經(jīng)被回收并且不再處于活躍狀態(tài)(例如當(dāng)前在對象池)時拋出異常
*/
public abstract void throwIfRecycled();
/**
* Sets whether or not our object is recycled.
* 標(biāo)記對象是否已經(jīng)被回收
* */
abstract void setRecycled(boolean isRecycled);
}
定義了需要可回收時的校驗接口Poolable
/**
* Allows additional verification to catch errors caused by using objects while they are in an
* object pool.
* 允許額外的校驗來捕獲因使用在對象池的對象而產(chǎn)生的異常岖沛,
*/
public interface Poolable {
@NonNull
StateVerifier getVerifier();
}
將上面的整合一下
private static final class FactoryPool<T> implements Pool<T> {
private final Factory<T> factory;
private final Resetter<T> resetter;
private final Pool<T> pool;
FactoryPool(@NonNull Pool<T> pool, @NonNull Factory<T> factory, @NonNull Resetter<T> resetter) {
this.pool = pool;
this.factory = factory;
this.resetter = resetter;
}
@Override
public T acquire() {
T result = pool.acquire();//先從對象池中獲取
if (result == null) {
result = factory.create();//沒獲取到則通過工廠方法來創(chuàng)建
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Created new " + result.getClass());
}
}
if (result instanceof Poolable) {
((Poolable) result).getVerifier().setRecycled(false /*isRecycled*/);//標(biāo)記不在對象池中
}
return result;
}
@Override
public boolean release(@NonNull T instance) {
if (instance instanceof Poolable) {
((Poolable) instance).getVerifier().setRecycled(true /*isRecycled*/);//標(biāo)記在復(fù)用池中
}
resetter.reset(instance);//重置狀態(tài)的相關(guān)操作
return pool.release(instance);//復(fù)用池復(fù)用
}
}
再對外提供工廠方法
package com.bumptech.glide.util.pool;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.util.Pools.Pool;
import androidx.core.util.Pools.SimplePool;
import androidx.core.util.Pools.SynchronizedPool;
import java.util.ArrayList;
import java.util.List;
/**
* Provides implementations of {@link Pool} never return {@code null}, log when new instances are
* created, and that can use the {@link com.bumptech.glide.util.pool.FactoryPools.Poolable}
* interface to ensure objects aren't used while inside the pool.
*/
public final class FactoryPools {
private static final String TAG = "FactoryPools";
private static final int DEFAULT_POOL_SIZE = 20;
private static final Resetter<Object> EMPTY_RESETTER =
new Resetter<Object>() {
@Override
public void reset(@NonNull Object object) {
// Do nothing.
}
};
private FactoryPools() {}
/**
* Returns a non-thread safe {@link Pool} that never returns {@code null} from {@link
* Pool#acquire()} and that contains objects of the type created by the given {@link Factory} with
* the given maximum size.
*
* <p>If the pool is empty when {@link Pool#acquire()} is called, the given {@link Factory} will
* be used to create a new instance.
* 線程不安全
* @param <T> The type of object the pool will contains.
*/
@NonNull
public static <T extends Poolable> Pool<T> simple(int size, @NonNull Factory<T> factory) {
return build(new SimplePool<T>(size), factory);
}
/**
* Returns a new thread safe {@link Pool} that never returns {@code null} from {@link
* Pool#acquire()} and that contains objects of the type created by the given {@link Factory} with
* the given maximum size.
*
* <p>If the pool is empty when {@link Pool#acquire()} is called, the given {@link Factory} will
* be used to create a new instance.
*線程安全
* @param <T> The type of object the pool will contains.
*/
@NonNull
public static <T extends Poolable> Pool<T> threadSafe(int size, @NonNull Factory<T> factory) {
return build(new SynchronizedPool<T>(size), factory);
}
/**
* Returns a new {@link Pool} that never returns {@code null} and that contains {@link List Lists}
* of a specific generic type with a standard maximum size of 20.
*
* <p>If the pool is empty when {@link Pool#acquire()} is called, a new {@link List} will be
* created.
*
* @param <T> The type of object that the {@link List Lists} will contain.
*/
@NonNull
public static <T> Pool<List<T>> threadSafeList() {
return threadSafeList(DEFAULT_POOL_SIZE);
}
/**
* Returns a new thread safe {@link Pool} that never returns {@code null} and that contains {@link
* List Lists} of a specific generic type with the given maximum size.
*
* <p>If the pool is empty when {@link Pool#acquire()} is called, a new {@link List} will be
* created.
*
* @param <T> The type of object that the {@link List Lists} will contain.
*/
// Public API.
@SuppressWarnings("WeakerAccess")
@NonNull
public static <T> Pool<List<T>> threadSafeList(int size) {
return build(
new SynchronizedPool<List<T>>(size),
new Factory<List<T>>() {
@NonNull
@Override
public List<T> create() {
return new ArrayList<>();
}
},
new Resetter<List<T>>() {
@Override
public void reset(@NonNull List<T> object) {
object.clear();
}
});
}
@NonNull
private static <T extends Poolable> Pool<T> build(
@NonNull Pool<T> pool, @NonNull Factory<T> factory) {
return build(pool, factory, FactoryPools.<T>emptyResetter());
}
@NonNull
private static <T> Pool<T> build(
@NonNull Pool<T> pool, @NonNull Factory<T> factory, @NonNull Resetter<T> resetter) {
return new FactoryPool<>(pool, factory, resetter);
}
}
相信通過上面這一些列的流程,我們對對象池的設(shè)計和實現(xiàn)有了一個大體的了解聘殖,同時對設(shè)計模式,算法的復(fù)雜度末盔,甚至后面的架構(gòu)設(shè)計積累了靈感和素材蛮粮,值得反復(fù)揣摩令哟。