头图
Raised in the pond: Object;

1. Design and Principle

1. Basic case

First, let's look at an application case based on the common-pool2 object pool component. There are mainly three core roles of factory class, object pool, and object, and the use process of pooled objects:

 import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ObjPool {
    public static void main(String[] args) throws Exception {
        // 声明对象池
        DevObjPool devObjPool = new DevObjPool() ;
        // 池中借用对象
        DevObj devObj = devObjPool.borrowObject();
        System.out.println("Idle="+devObjPool.getNumIdle()+";Active="+devObjPool.getNumActive());
        // 使用对象
        devObj.devObjInfo();
        // 归还给对象池
        devObjPool.returnObject(devObj);
        System.out.println("Idle="+devObjPool.getNumIdle()+";Active="+devObjPool.getNumActive());
        // 查看对象池
        System.out.println(devObjPool.listAllObjects());
    }
}
/**
 * 对象定义
 */
class DevObj {
    private static final Logger logger = LoggerFactory.getLogger(DevObj.class) ;
    public DevObj (){
        logger.info("build...dev...obj");
    }
    public void devObjInfo (){
        logger.info("dev...obj...info");
    }
}
/**
 * 对象工厂
 */
class DevObjFactory extends BasePooledObjectFactory<DevObj> {
    @Override
    public DevObj create() throws Exception {
        // 创建对象
        return new DevObj() ;
    }
    @Override
    public PooledObject<DevObj> wrap(DevObj devObj) {
        // 池化对象
        return new DefaultPooledObject<>(devObj);
    }
}
/**
 * 对象池
 */
class DevObjPool extends GenericObjectPool<DevObj> {
    public DevObjPool() {
        super(new DevObjFactory(), new GenericObjectPoolConfig<>());
    }
}

In the case, the object is completely customized; in the object factory, two core methods are rewritten: creation and packaging to create a pooled object; the construction of the object pool depends on the defined object factory, and the configuration adopts the general configuration class provided by the component; You can initially understand the principle of object pooling by adjusting the time of object instantiation and the number of objects created.

2. Interface design

1.1 PooledObjectFactory interface

  • Factory class, responsible for object instantiation, creation, verification, destruction, state management, etc.;
  • In the case BasePooledObjectFactory类 is the basic implementation of the interface;

1.2 ObjectPool interface

  • Object pool, and inherit Closeable interface, manage object life cycle, and obtain data information of active and idle objects;
  • In the case GenericObjectPool类 is the implementation of this interface, and it is a configurable way;

1.3 PooledObject interface

  • Pooled objects are maintained in the object pool based on the wrapper class, and maintain some additional information for tracking, such as time, status;
  • In the case, the DefaultPooledObject wrapper class is used to implement this interface and it is thread-safe. Pay attention to the rewriting in the factory class;

3. Operating principle

The object obtained through the object pool may be newly created through the factory, or it may be an idle object; when the object is successfully obtained and used, the object needs to be returned; during the execution of the case, the number of idle and active objects in the object pool is continuously queried. Quantity to monitor changes to the pool.

2. Structural Analysis

1. Object pool

 public GenericObjectPool(final PooledObjectFactory<T> factory,final GenericObjectPoolConfig<T> config);

In the complete construction method, three core objects are involved: factory object, configuration object, double-ended blocking queue; a new object pool is created through these objects; some simple default configurations are provided in config: such as maxTotal , maxIdle, minIdle, etc., you can also extend the custom configuration;

2. Double-ended queue

 private final LinkedBlockingDeque<PooledObject<T>> idleObjects;
public GenericObjectPool(final PooledObjectFactory<T> factory,final GenericObjectPoolConfig<T> config) {
    idleObjects = new LinkedBlockingDeque<>(config.getFairness());
}

LinkedBlockingDeque supports operating elements at the beginning and end of the queue, such as adding and removing; the operation needs to be locked through the main lock, and cooperates based on two state locks;

 // 队首节点
private transient LinkedBlockingDeque.Node<E> first;
// 队尾节点
private transient LinkedBlockingDeque.Node<E> last;
// 主锁
private final InterruptibleReentrantLock lock;
// 非空状态锁
private final Condition notEmpty;
// 未满状态锁
private final Condition notFull;

The characteristics of linked lists and queues have been analyzed separately in previous articles. The source code here is also very common in JDK containers. I will not repeat them here. After the entire structure of the object pool has a general outline, let’s take a closer look at the management logic.

3. Object management

1. Add objects

Create a new object and put it into the pool, usually used in scenes that need to be preloaded ; it involves two core operations: factory creation of objects, object pooling management;

 public void GenericObjectPool.addObject() throws Exception ;

2. The borrowed object

 public T GenericObjectPool.borrowObject(final long borrowMaxWaitMillis) throws Exception ;

First obtain the object from the queue; if not obtained, call the factory creation method, and then pool management; after the object is obtained, the state will change to ALLOCATED in use; finally, after the confirmation of the factory, the object acquisition action is completed;

3. The object of return

 public void GenericObjectPool.returnObject(final T obj) ;

When returning an object, it is first converted to a pooled object and a tag RETURNING state; after multiple verifications and judgments, if it fails, the object will be destroyed and the free objects available in the object pool will be re-maintained; Marked as idle, if the maximum idle number is not exceeded, the object is placed at one end of the queue;

4. Object status

The status of the pooled object is enumerated and described in the PooledObjectState class. In the figure, only a few state flows are illustrated. For more details, please refer to the status class;

You can refer to the relevant methods in the default pooling object class used in the above case DefaultPooledObject combined with state enumeration, you can understand the checksum conversion between different states.

4. Redis application

As an advanced client component of Redis, Lettuce uses Netty components for the communication layer, which is thread-safe, supports synchronous and asynchronous modes, and supports cluster and sentinel modes. As a commonly used configuration in current projects, its underlying object pool is based on common-pool2 .

1. Configuration management

Based on the following configuration, the Lettuce component is used, which involves several parameter configurations of the pool: minimum idle, maximum active, and maximum idle; here you can compare the configuration in GenericObjectPoolConfig:

 spring:
  redis:
    host: ${REDIS_HOST:127.0.0.1}
    lettuce:
      pool:
        min-idle: 10
        max-active: 100
        max-idle: 100

2. Source code analysis

Focusing on the characteristics of the object pool, it is natural to search for the core role classes in the source code: configuration, factory, and object; from the above configuration parameters, it is easy to find the following classes:

2.1 Configuration Transformation

 // 连接配置
class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
    private static class PoolBuilderFactory {
        // 构建对象池配置
        private GenericObjectPoolConfig<?> getPoolConfig(RedisProperties.Pool properties) {
            GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>();
            config.setMaxTotal(properties.getMaxActive());
            config.setMaxIdle(properties.getMaxIdle());
            config.setMinIdle(properties.getMinIdle());
            return config;
        }
    }
}

Here, the relevant parameters of Redis in the configuration file are built into the GenericObjectPoolConfig class, that is, the configuration loading process;

2.2 Object pool construction

 class LettucePoolingConnectionProvider implements LettuceConnectionProvider {
    // 对象池核心角色
    private final GenericObjectPoolConfig poolConfig;
    private final BoundedPoolConfig asyncPoolConfig;
    private final Map<Class<?>, GenericObjectPool> pools = new ConcurrentHashMap(32);
    LettucePoolingConnectionProvider(LettuceConnectionProvider provider, LettucePoolingClientConfiguration config) {
        this.poolConfig = clientConfiguration.getPoolConfig();
        this.asyncPoolConfig = CommonsPool2ConfigConverter.bounded(this.config);
    }
}

The configuration information of the object pool is obtained in the construction method. The pool object is not directly instantiated here, but the ConcurrentHashMap container is used for dynamic maintenance;

2.3 Object Management

 class LettucePoolingConnectionProvider implements LettuceConnectionProvider {
    // 获取Redis连接
    public <T extends StatefulConnection<?, ?>> T getConnection(Class<T> connectionType) {
        GenericObjectPool pool = (GenericObjectPool)this.pools.computeIfAbsent();
        StatefulConnection<?, ?> connection = (StatefulConnection)pool.borrowObject();
    }
    // 释放Redis连接
    public void release(StatefulConnection<?, ?> connection) {
        GenericObjectPool<StatefulConnection<?, ?>> pool = (GenericObjectPool)this.poolRef.remove(connection);
    }
}

When acquiring a pool object, if it does not exist, create a pool object according to the relevant configuration, maintain it in the Map container, and then borrow the Redis connection object from the pool; when releasing an object, first determine the pool to which the object belongs, and return the object to the corresponding pool middle.

In conclusion , this article starts from a simple case of object pool, mainly analyzes the source code logic of several roles of pool, factory, configuration, object management, and refers to its practice in common-pool2 , just The tip of the iceberg, such a general-purpose and wide-ranging component, is worth reading the source code from time to time. It is really amazing for its ingenious design.

5. Reference source code

 应用仓库:
https://gitee.com/cicadasmile/butte-flyer-parent

组件封装:
https://gitee.com/cicadasmile/butte-frame-parent


已注销
479 声望53 粉丝