Skip to content

性能优化-对象池

About 1109 wordsAbout 4 min

性能新书

2026-08-21

5.2 对象池

在上文提到的重量级对象特征是构造复杂,在使用此对象过程中,对象会缓存中间处理过程,如Jackson的ObjectMapper,Beetl的GroupTemplate,还有Gson序列化工具等等,还有一类重量级对象则是网络相关的客户端对象,如数据库连接池,以及Redis客户端Jedis使用JedisPool,这类是重量级别对象原因是建立网络链接是非常耗时的操作,应用希望一旦建立后,就会保持此链接。这时候,就需要使用池化技术。池化技术提供了对象重用机制

使用池化技术另外一个原因是减少对象的创建个数,总是复用之前创建的对象,从而减小垃圾回收时间

现在有一个对象链接Redis,定义如下

public class RedisClient {
    String ip;
    int port;
    public RedisClient(String ip,int port){
        this.ip = ip;
        this.port = port;
    }
    public void connect(){
        System.out.println("connect redis ");
    }
    
    public void close(){
        System.out.println("close  ");
    }
}

connect方法将会链接Redis 服务器,close方法则会管理链接,为了简单期间,省略了socket链接代码,RedisClient是一个重量级对象,可以使用使用Apache Commons Pool来实现对象池化,Commons Pool提供了BasePooledObjectFactory 可以方便的创建和管理对象,如下定义

public class RedisClientPooledObjectFactory extends BasePooledObjectFactory<RedisClient> {
        String ip;
        int port;
        public RedisClientPooledObjectFactory(String ip,int port){
                this.ip = ip;
                this.port = port;
        }

        @Override
        public RedisClient create() throws Exception {
                RedisClient redisClient =  new RedisClient(ip,port);
                redisClient.connect();
                return redisClient;
        }

        @Override
        public PooledObject<RedisClient> wrap(RedisClient redisClient) {
                return new DefaultPooledObject<RedisClient>(redisClient);
        }

        @Override
        public void destroyObject(PooledObject<RedisClient> p) throws Exception{
                RedisClient redisClient = p.getObject();
                redisClient.close();
        }
}

通常需要实现create方法,用于创建重量级对象,wrap方法用于对象池管理,对象池不直接管理我们创建的对象,而是管理PooledObject,可以用DefaultPooledObject构造一个默认实现。destroyObject方法用于对象池销毁的时候,关闭重量级对象

有了RedisClientPooledObjectFactory,就可以构造GenericObjectPool,

public class RedisClientPoolUtil {
        GenericObjectPool<RedisClient> genericObjectPool;
        public void init(String ip,int port,int max){
                GenericObjectPoolConfig config = new GenericObjectPoolConfig();
                config.setMaxTotal(max);
                genericObjectPool = new GenericObjectPool(new RedisClientPooledObjectFactory(ip,port),config);
        }

        public RedisClient getOne() throws Exception {
                return genericObjectPool.borrowObject();
        }

        public void returnObject(RedisClient redisClient){
                genericObjectPool.returnObject(redisClient);
        }
}

如上代码构造了最多构造5个RedisClient的对象池,并封装了getOne方法用于从池里借出一个未使用的RedisClient,returnObject,把RedisClient归还到池里,如下代码测试

public class PoolTest {
        static RedisClientPoolUtil util = new RedisClientPoolUtil();
        static{
                util.init("127.0.0.1",9090,3);
        }
        public static void main(String[] args) throws Exception {
                RedisClient redisClient1 = util.getOne();
                RedisClient redisClient2 = util.getOne();
                RedisClient redisClient3 = util.getOne();
                //会阻塞住,因为总共创建了3个
                RedisClient redisClient4 = util.getOne();
        }
}

除了使用Common Pool技术外,还有更为简单的池化技术。比如用队列放入已经初始化好的对象。需要的时候从队列里取出,使用完毕放回队列。另外一个更为简单的方式是使用ThreadLocal来存放初始化好的对象。假定需要池化对象是要给字符数组,使用队列方式

/*定义一个需要重用得对象*/
public class Buffer{
    char[]  cs;
    public Buffer(int size){
        cs = new char[size];
    }
    public char[] getContent(){
        return cs;
    }
}

使用队列方式

public class BufferPool{
   prvate static BufferPool pool = new BufferPool();
   ArrayBlockingQueue<Buffer>  queue ;
   private BufferPool(){
       init();
   }
   /*初始化buffer*/ 
   private void init(){
       queue = new ArrayBlockingQueue<>(64);
       for(int i=0;i<64;i++){
           queue.put(new Buffer(1024);
       }
   }
   /*采用单例模式*/ 
   public static BufferPool instance(){
       return this
   }
   public Buffer getBuffer(){
       return queue.take()
   }
   public void release(Buffer buffer){
       queue.put(buffer);
   }
   /*清空池*/                  
   public void close(){
       queue.clear()
   }
}

使用ThreadLocal方式如下

public class BufferPool{
   static  ThreadLocal<Buffer cache = ThreadLocal.withInitial((Supplier) () - new Buffer(1024));
   public  static Buffer getBuffer(){return cache.take()}}

使用队列方式得优点是可以提供扩容”池“功能,缩容池,甚至释放池的功能。缺点是使用完毕必须归还,因此调用代码应该如下方式

public class BufferPool{
   static  ThreadLocal<Buffer> cache = ThreadLocal.withInitial((Supplier) () -> new Buffer(1024));
   public  static Buffer getBuffer(){
       return cache.take()
   }
}

使用ThreadLocal的缺点是池的容量大小依赖于线程数,池的的容量是否可控,取决于调用时候的线程数,其创建和销毁依赖于线程的生命周期

ThreadLocal 不具备动态调整池大小功能,依赖线程数大小,如果担心内存溢出,可以使用SoftReference

public class BufferPool{
  static  ThreadLocal<SoftReference<Buffer>> cache = ThreadLocal.withInitial((Supplier) () -> 
                  new SoftReference (new Buffer(1024)));
  
  public  static Buffer getBuffer(){
    SoftReference<Buffer> srf =  cache.take();
    Buffer buffer = srf.get();
    //判断是否被垃圾回收
    if(buffer!=null}{
      return buffer;
    }
    //如果回收了,重新构造一个   
    Buffer buffer = new Buffer(1024)   ;
    srf.set(buffer);
    return buffer;   
  }
}

知行合一