关于对象池的一些分析和总结

本文探讨了对象池的设计原则与应用场景,对比了其与本地缓存的区别,并以OkHttp的网络连接池为例,深入分析了对象池的具体实现,包括数据结构的选择、生命周期管理和性能优化策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

定义

有借有还,先放在这,突然觉的这句话与对象池很配!!!

将具有生命周期期的结构化对象缓存到带有一定管理功能的容器中,以提高对象的访问性能。

对象池与普通的本地 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 这样设计吗?

看到对象池,我联想到的是工厂车间:车间相当于一个对象池,工人类似于一个对象。工作期间:假如有一个领导把工人从车间出来去完成一件事,车间里面就会少一个人,管理员需要记录一下;当借出去的工人完成后,又被领导给车间,此时车间里面增加了一个人,管理员又记录了一次。如果效益不好,工厂倒闭了,那么工人肯定都走了,那么车间肯定被清空了。

以上流程只是最简单的一个流程,期间一些细节,如:如果一个工人被两个领导同时调离车间(并发)?工人如果工作中间请假了,那么领导去借他时,怎么做?车间的管理员的职责是定义为单一的还是多元的(除了记录每个工人的状态,他还用干其他活吗)?等等

总结

设计对象池时,我们应该注意:

  1. 我们应该根据业务设置对象池的大小,对象池太大,空闲对象的数量太多,就会增加内存消耗;对象池太小,没有可用的对象时,我们是抛出异常还是返回一个null。
  2. 对象池使用什么样的数据结构?是使用数组,还是链表,还是散列表等等。我们应该根据自己的业务来选择合适的数据结构
  3. 脏对象。类比车间场景:一个工人被借出去换了一身衣服,当了掏粪工,完成后,他想穿着那一身衣服进车间,百分百的不行啊。这身衣服就是脏数据。所以,还对象必须将对象重置为之前的状态。

实例 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;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值