我们经常会遇到使用对象复用的场景,比如线程池,数据库连接池,Android的消息机制里面的Message的通过静态方法obtain()
获取消息,EventBus
在的解析监听事件的订阅者对象里的标有@subscribe
的方法而引入的SubscriberMethodFinder.FindState对象池FIND_STATE_POOL
,Glide
在请求加载图像的时候EngineJob内部的Pools.Pool<EngineJob<?>> pool
和解码DecodeJob内部的Pools.Pool<DecodeJob<?>> pool
。因为对象创建的开销过大,为了避免每一次用到某个对象的时候都去new
一个新的对象。对象池提供了这样一种机制,当我们需要某个对象的时候,我们希望先尝试从对象池(相当于缓存)中获取,如果有就直接返回,此时对象池应该空一个位子出来(即对象池的元素个数减一),没有则新创建一个对象返回,当对象用完以后,如果对象池还有空余位置则存放入其中同时对象池中元素的个数+1。
如何实现一个对象池呢?软件开发中经常说的一句话是面向接口而不是面向实现编程,因此将对象池抽象成一个接口。由于接口是功能的集合,对象池应该对外提供哪些功能呢?既然是对象池,那么它必然有一个获取对象和回收对象的方法。由于我们希望它是一个通用型的接口,对任意的类型都适用,任意类型都能实现获取和回收,因此需要用到泛型,我们用Pool
来表示对象池,用acquire
来表示获取对象,用recycle
来表示回收对象,于是就有可以得到下面这个接口。
public interface Pool<T> {
//从对象池中获取对象
T acquire();
//将不需要的对象放回对象池,返回值boolen类型表示是否回收成功
boolean recycle(T t);
}
有了上面的接口,我们就来尝试何实现一个简单的对象池。既然是对象池,必然容量是有限的,同时必须大于0,如何存放对象呢,最简单的方式是用数组。我们获取对象的时候就查询一下这个数组,看看其中有没有可用的元素,有就返回此元素同时将该处位原来的元素置空,不然会出现内存泄漏,没有就返回null
。回收对象的时候先遍历一下数组是不是已经填满了,没有填满,就放入数组中,于是有下面的简单实现。
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;//置空,避免内存泄漏
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;
}
}
有了这个简单的设计之后,就可以使用了,一般都是将这个类简单的设计在某个工具类中使用,我们不妨定义一个工具类叫PoolUtils
,定义要复用的对象类型为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(); //没有就创建新的
}
//回收对象
public static boolean recycle(HeavyObject object) {
return sPool.recycle(object);
}
}
在回头看看我们实现的SimplePool
设计得如何呢?emmm,我们发现aquire
和recycle
方法总会从从头到尾到数组的遍历。假如我们的缓存数组mElements
的大小为n
,开始时数组里面其实并没有元素,此时aquire
方法的时间复杂度读为O(n)
(并且还没有获取到元素),recycle
方法的时间复杂度为O(1)
(放在下标为0的位置),而当数组mElements
中填满元素后,aquire
方法的时间复杂度读为O(1)
(返回下标为0处的元素),recycle
方法的时间复杂度为O(n)
(遍历完数组后仍然不能存放)。我们希望aquire
和recycle
方法无论在mElements
是否有数据的情况下都能做到时间复杂度为O(1)
,那么如何做到这一点呢?
不难通过分析发现,aquire
和recycle
方法只要记住上次存取的位置就行了,我们将元素连续存放,比如上次是在下标为i
处存放了元素(下标从0~i
都有数据),那么aquire
则从i
处获取,recycle
方法则在i+1
处存放,只要i+1<n
.实际上i+1
即为缓存池里面已经存放的元素的个数,于是我们引入一个变量mPoolSize
用于记录缓存池里面已经存放的元素个数。对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;//置空,避免内存泄漏
mPoolSize--;
return t;
}
return null;
}
@Override
public boolean recycle(T t) {
if (mPoolSize < mMaxPoolSize) {
mElements[mPoolSize] = t;
mPoolSize++;
return true;
}
return false;
}
}
通过重构,现在的acquire
和recycle
方法只需要做一次判断,而不用从头开始遍历数组,因此时间复杂度都成了O(1)
.
看起来一切都很完美了,但是事情好像并没有那么简单,在多线程的情况下,比如某一时刻A线程正在执行aquire
操作,B线程正在执行recycle
操作很容易出现很容出现数据不一致的的安全性问题。看来我们得考虑一下线程安全的问题了。
为了能给用户提供一个分别线程安全和线程不安全的对象池,我们将线程安全的对象池的实现命名为SynchronizedPool
,并给出实现
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;//置空,避免内存泄漏
mPoolSize--;
return instance;
}
return null;
}
}
@Override
public boolean recycle(T t) {
synchronized (mLock) {
if (mPoolSize < mMaxPoolSize) {
mElements[mPoolSize] = t;
mPoolSize++;
return true;
}
return false;
}
}
}
于是我们能够提供了一个用户自己选择的对象池创建工厂方法,,让用户自定义是需要线程安全的还是线程不安全的版本:
class PoolFactory {
public static <T> Pool<T> createPool(boolean threadSafe, int maxPoolSize) {
return threadSafe ? new SynchronizedPool<T>(maxPoolSize) : new SimplePool<T>(maxPoolSize);
}
}
看到到这里读者会问,这和标题有什么关系?实际上androidX已经提供了一个内置的对象复用池,如下所示
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;//置空,避免内存泄漏
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);
}
}
}
}
通过对比我们的设计和androidX的设计,发现androidX的SimplePool多了一个isInPool
方法,也就是校验是否复用池里面已经有相同的对象了,如果存在就没有必要再放入一个元素,不然也就失去了复用的意义。同时SynchronizedPool的方法的实现是继承了SimplePool,只是在复写acquire
和release
方法的时候加了一个锁,这样代码的复用率更高了。
如果读者有看过Glide
源码的话,会发现Glide
我们前面的PoolFactory
上面更进了一步,它提供了将工厂模式和对象池复用的精妙操作,一起来学习一下。
定义一个工厂接口Factory
/**
* Creates new instances of the given type.
* 创建指定类型的新的实例
*
* @param <T> The type of Object that will be created.
*/
public interface Factory<T> {
T create();
}
定义了一个对象被放回对象池时可能进行重置状态操作的接口Resetter
/**
* Resets state when objects are returned to the pool.
* 当对象放回复用池时重置状态
*
* @param <T> The type of Object that will be reset.
*/
public interface Resetter<T> {
void reset(@NonNull T object);
}
定义了处理回收标记的抽象类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).
* 如果我们确信我们的对象已经被回收并且不再处于活跃状态(例如当前在对象池)时抛出异常
*/
public abstract void throwIfRecycled();
/**
* Sets whether or not our object is recycled.
* 标记对象是否已经被回收
* */
abstract void setRecycled(boolean isRecycled);
}
定义了需要可回收时的校验接口Poolable
/**
* Allows additional verification to catch errors caused by using objects while they are in an
* object pool.
* 允许额外的校验来捕获因使用在对象池的对象而产生的异常,
*/
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();//没获取到则通过工厂方法来创建
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Created new " + result.getClass());
}
}
if (result instanceof Poolable) {
((Poolable) result).getVerifier().setRecycled(false /*isRecycled*/);//标记不在对象池中
}
return result;
}
@Override
public boolean release(@NonNull T instance) {
if (instance instanceof Poolable) {
((Poolable) instance).getVerifier().setRecycled(true /*isRecycled*/);//标记在复用池中
}
resetter.reset(instance);//重置状态的相关操作
return pool.release(instance);//复用池复用
}
}
再对外提供工厂方法
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);
}
}
相信通过上面这一些列的流程,我们对对象池的设计和实现有了一个大体的了解,同时对设计模式,算法的复杂度,甚至后面的架构设计积累了灵感和素材,值得反复揣摩。