定义
有借有还,先放在这,突然觉的这句话与对象池很配!!!
将具有生命周期期的结构化对象缓存到带有一定管理功能的容器中,以提高对象的访问性能。
对象池与普通的本地 cachce 缓存策略有什么不同? 第一:本地 cache常常会有一些失效策略,比如按照时间,访问量等,而 对象池是没有这些特性的;第二:缓存中的对象是没有一个完整生命周期的概念,而对象池中的对象是具有完整生命周期的,而且我们还可以管理这些生命周期。
从上面不难看出,对象池的目的: 减少频繁创建和销毁对象所带来的性能消耗,实现对象的缓存与复用。看到这里,有没有觉得它其实是一种牺牲空间换取时间的策略。
从对象池的目的,我们也可以猜出它使用的场景:如果对象的创建与销毁比较消耗性能(如:线程)或者创建对象的操作比较频繁,就可以使用对象池。经典案例:线程池,okhttp 的连接池,glide 的bitmap池,handler 的 message池等等。
如何设计一个对象池
我们可以看下 Android 中提供的对象池
pulbic final class Pools {
public interface Pool<T> {
T acquire(); // 从池中借一个对象
boolean release(@NonNull T instance); // 将借的对象还给对象池
}
private Pools();
public static class SimplePool<T> implements Pool<T> {
private final Object[] mPool; // 对象池使用的数据结构
private int mPoolSize; // 当前对象池中的对象个数
public SimplePool(int maxPoolSize) {
assert (maxPoolSize <= 0):("The max pool size must be > 0")
mPool = new Object[maxPoolSize]();
}
@Override public T acquire() {
synchronized(this) {
if(mPoolSize > 0) {
final int lastPoolIndex = mPoolSize - 1;
T instance = (T)mPool[lastPoolIndex];
mPool[lastPoolIndex] = null;
mPoolSize--;
return instance;
}
}
return null;
}
@Override public boolean release(@NonNull T instance) {
synchronized(this) {
// 如果该对象已经在池中,则抛异常
if(isInPool(instance)) {
throw new IllegalStateException("Already in the pool!");
}
// 如果当前size小于对象池的最大容量,则将对象加入对象池
if(mPoolSize < mPool.length) {
mPool[mPoolSize] = instance;
mPoolSize++;
return true;
}
return false;
}
}
private boolean isInPool(T instance) {
for(int i =0; i < mPoolSize; i++) {
if(mPool[i] == instance) {
return true;
}
}
return false;
}
}
}
假如,让我们设计一个对象池,你会向 Android 这样设计吗?
看到对象池,我联想到的是工厂车间:车间相当于一个对象池,工人类似于一个对象。工作期间:假如有一个领导把工人从车间借出来去完成一件事,车间里面就会少一个人,管理员需要记录一下;当借出去的工人完成后,又被领导还给车间,此时车间里面增加了一个人,管理员又记录了一次。如果效益不好,工厂倒闭了,那么工人肯定都走了,那么车间肯定被清空了。
以上流程只是最简单的一个流程,期间一些细节,如:如果一个工人被两个领导同时调离车间(并发)?工人如果工作中间请假了,那么领导去借他时,怎么做?车间的管理员的职责是定义为单一的还是多元的(除了记录每个工人的状态,他还用干其他活吗)?等等
总结:
设计对象池时,我们应该注意:
- 我们应该根据业务设置对象池的大小,对象池太大,空闲对象的数量太多,就会增加内存消耗;对象池太小,没有可用的对象时,我们是抛出异常还是返回一个null。
- 对象池使用什么样的数据结构?是使用数组,还是链表,还是散列表等等。我们应该根据自己的业务来选择合适的数据结构
- 脏对象。类比车间场景:一个工人被借出去换了一身衣服,当了掏粪工,完成后,他想穿着那一身衣服进车间,百分百的不行啊。这身衣服就是脏数据。所以,还对象必须将对象重置为之前的状态。
实例 OkHttp的网络连接池
Okhttp 的网络连接池使用的就是对象池,它在使用 findConnection 获取一个有效连接时,会先从连接池中查找 connecitonPool.get() ,如果找到则直接使用;如果没有匹配到,则创建一个新的 connection,并执行 connectionPool.put(),添加进连接池。当连接使用完后,并不会立即关闭连接,而是将该连接置为空闲连接,使用一个清理线程去清理空闲连接,每一个空闲连接的存活时间是5分钟。
代码实现:
public final class ConnectionPool {
// 使用线程池
private static final Executor executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
private final int maxIdleConnections; // 每个地址的最大空闲连接数
private final int keepAliveDurationsNs; // 纳秒级
private final Runnable cleanupRunnable = new Runnable() {
@override public void run(){
while(true) {
long waitNanos = cleanup(System.nanoTime());
if (waitNanos == -1) return;
if (waitNanos > 0) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);
synchronized (ConnectionPool.this) {
try {
// 超时等待,到时间后,自动通知清理线程
ConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
}
}
}
}
}
// 使用数组双端队列作为连接池的数据结构,缓存连接
private final Deque<RealConnection> connections = new ArrayDeque<>();
// 路由数据
final RouteDataBase routeDataBase = new RouteDataBase();
boolean cleanupRunning;
// 默认空闲连接是5个,每个连接的存活时间是5分钟
public ConnectionPool() {
this(5, 5, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, int keepAliveDuration, TimeUnit timeUnit) {
this.maxIdleConnections = maxIdleConnections;
this.keepAliveDurationsNs = timeUnit.toNanos(keepAliveDuration);
assert keepAliveDuration <= 0:("keepAliveDuration <= 0: " + keepAliveDuration);
}
// 当前空闲连接的数量
public synchronized int idleConnectionCount() {
int count = 0;
for(RealConnection connection: connections) {
if(connection.isEmpty()) count++;
}
return count;
}
// 当前连接数量
pbulic synchronized int connectionCount() {
return connections.size();
}
@Nullable Socket deduplicate(Address address, StreamAllocation streamAllocation) {
asset (Thread.holdsLock(this));
// 释放并重新请求连接
for(RealConnection connection: connections) {
if(connection.isEligible(address, nulll)
&& connection.isMultiplexed()
&& connection != streamAllocation.connection()) {
return streamAllocation.releaseAndAcquire(connection);
}
}
return null;
}
// 从池子中借一个池对象
@Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
// 断言当前线程是持有对象锁 this 的线程吗
assert (Thread.holdsLock(this));
// 是
for(RealConnection connection: connections) {
// 如果网络地址和路由都合格
if(connection.isEligible(address, route)) {
// 呼叫配对
streamAllocation.acquire(conenction, true);
return connection; // 返回一个有效连接
}
}
return null;
}
// 往池子里面添加连接
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
// 如果清理线程没有开始,则开始清理线程
if(!cleanupRunning) {
cleanupRunning = true;
executor.execute(cleanupRunnable);
}
// 加入双端队列
connections.add(connection);
}
// 将借出去的对象还回来
boolean connectionBecameIdle(RealConection connection) {
assert (Thread.holdsLock(this));
// 如果该连接无法创建新的流或者设置的最大空闲连接池数为0,则从池中移除该连接
if(connection.noNewStreams || maxIdleConnections == 0) {
connections.remove(connection);
return true;
} else {
notifyAll(); // 通知清理线程,此时可能空闲连接数超出了5个
return false;
}
}
// 从连接池中移除并关闭所有的连接
public void evictAll() {
List<RealConnection> evictedConnections = new ArrayList();
synchronized(this) {
for(Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
if(connection.allocations.isEmpty()) {
connection.noNewStreams = true; // 该连接标记为不可用
evictedConnections.add(connection);
i.remove(); // 从对象池中移除
}
}
}
for(RealConnection connection: evictedConnections) {
closeQuietly(connection.socket()); // 关闭连接
}
}
// 在一个线程循环中执行
int cleanup() {
int inUseConnectionCount = 0;
int idleConnectionCount = 0;
RealConnection longestIdleConnection = null;
int longestIdleConnectionCount = Long.MIN_VALUE;
// 1. 遍历计算连接池内出存活时间最长得到连接
synchronized(this) {
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++;
continue;
}
idleConnectionCount++;
long idleDurationNs = now - connection.idleAtNanos;
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs;
longestIdleConnection = connection;
}
}
// 1. 如果最长空闲连接的存活时间超出指定的存活时间 或者 空闲连接的数量大于设置的最大空闲数量, 则移除最长存活空闲连接
if (longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections) {
connections.remove(longestIdleConnection);
} else if (idleConnectionCount > 0) { // 2. 如果有空闲连接,则返回需要等待的时间
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) { // 3. 如果有正在使用的连接
return keepAliveDurationNs;
} else { // 4. 连接池内部没有连接,则停止清理线程,并重置清理标志位
cleanupRunning = false;
return -1;
}
// 关闭连接
closeQuietly(longestIdleConnection.socket());
// 重新开始清理
return 0;
}
}