池塘里養:Object;
一、設計與原理
1、基礎案例
首先看一個基于common-pool2物件池組件的應用案例,主要有工廠類、物件池、物件三個核心角色,以及池化物件的使用流程:
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<>());
}
}
案例中物件是完全自定義的;物件工廠中則重寫兩個核心方法:創建和包裝,以此創建池化物件;物件池的構建依賴定義的物件工廠,配置采用組件提供的常規配置類;可以通過調整物件實體化的時間以及創建物件的個數,初步理解物件池的原理,
2、介面設計
1.1 PooledObjectFactory 介面
- 工廠類,負責物件實體化,創建、驗證、銷毀、狀態管理等;
- 案例中
BasePooledObjectFactory類則是該介面的基礎實作;
1.2 ObjectPool 介面
- 物件池,并且繼承
Closeable介面,管理物件生命周期,以及活躍和空閑物件的資料資訊獲取; - 案例中
GenericObjectPool類是對于該介面的實作,并且是可配置化的方式;
1.3 PooledObject 介面
- 池化物件,基于包裝類被維護在物件池中,并且維護一些附加資訊用來跟蹤,例如時間、狀態;
- 案例中采用
DefaultPooledObject包裝類,實作該介面并且執行緒安全,注意工廠類中的重寫;
3、運行原理

通過物件池獲取物件,可能是通過工廠新創建的,也可能是空閑的物件;當物件獲取成功且使用完成后,需要歸還物件;在案例執行程序中,不斷查詢物件池中空閑和活躍物件的數量,用來監控池的變化,
二、構造分析
1、物件池
public GenericObjectPool(final PooledObjectFactory<T> factory,final GenericObjectPoolConfig<T> config);
在完整的構造方法中,涉及到三個核心物件:工廠物件、配置物件、雙端阻塞佇列;通過這幾個物件創建一個新的物件池;在config中提供了一些簡單的默認配置:例如maxTotal、maxIdle、minIdle等,也可以擴展自定義配置;
2、雙端佇列
private final LinkedBlockingDeque<PooledObject<T>> idleObjects;
public GenericObjectPool(final PooledObjectFactory<T> factory,final GenericObjectPoolConfig<T> config) {
idleObjects = new LinkedBlockingDeque<>(config.getFairness());
}
LinkedBlockingDeque支持在佇列的首尾操作元素,例如添加和移除等;操作需要通過主鎖進行加鎖,并且基于兩個狀態鎖進行協作;
// 隊首節點
private transient LinkedBlockingDeque.Node<E> first;
// 隊尾節點
private transient LinkedBlockingDeque.Node<E> last;
// 主鎖
private final InterruptibleReentrantLock lock;
// 非空狀態鎖
private final Condition notEmpty;
// 未滿狀態鎖
private final Condition notFull;
關于鏈表和佇列的特點,在之前的文章中有單獨分析過,此處的原始碼在JDK的容器中也很常見,這里不在贅述,物件池的整個構造有大致輪廓之后,下面再來細看物件的管理邏輯,
三、物件管理
1、添加物件
創建一個新物件并且放入池中,通常應用在需要預加載的場景中;涉及到兩個核心操作:工廠創建物件,物件池化管理;
public void GenericObjectPool.addObject() throws Exception ;
2、借用物件
public T GenericObjectPool.borrowObject(final long borrowMaxWaitMillis) throws Exception ;

首先從佇列中獲取物件;如果沒有獲取到,呼叫工廠創建方法,之后池化管理;物件獲取之后會改變狀態為ALLOCATED使用中;最后經過工廠的確認,完成物件獲取動作;
3、歸還物件
public void GenericObjectPool.returnObject(final T obj) ;

歸還物件的時候,首先轉換為池化物件和標記RETURNING狀態;經過多次校驗判斷,如果失敗則銷毀該物件,并重新維護物件池中可用的空閑物件;最終物件被標記為空閑狀態,如果不超出最大空閑數,則物件被放到佇列的某一端;
4、物件狀態
關于池化物件的狀態在PooledObjectState類中有列舉和描述,在圖中只是對部分幾個狀態流轉做示意,更多細節可以參考狀態類;

可以參考在上述案例中使用到的DefaultPooledObject默認池化物件類中相關方法,結合狀態列舉,可以理解不同狀態之間的校驗和轉換,
四、Redis應用
Lettuce作為Redis高級的客戶端組件,通信層使用Netty組件,并且是執行緒安全,支持同步和異步模式,支持集群和哨兵模式;作為當下專案中常用的配置,其底層物件池基于common-pool2組件,
1、配置管理
基于如下配置即表示采用Lettuce組件,其中涉及到池的幾個引數配置:最小空閑、最大活躍、最大空閑;這里可以對比GenericObjectPoolConfig中的配置:
spring:
redis:
host: ${REDIS_HOST:127.0.0.1}
lettuce:
pool:
min-idle: 10
max-active: 100
max-idle: 100
2、原始碼分析
圍繞物件池的特點,自然去追尋原始碼中關于:配置、工廠、物件幾個核心的角色類;從上述配置引數切入,可以很容易發現如下幾個類:

2.1 配置轉換
// 連接配置
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;
}
}
}
這里將組態檔中Redis的相關引數,構建到GenericObjectPoolConfig類中,即配置加載程序;
2.2 物件池構造
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);
}
}
在構造方法中獲取物件池的配置資訊,這里并沒有直接實體化池物件,而是采用ConcurrentHashMap容器來動態維護;
2.3 物件管理
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);
}
}
在獲取池物件時,如果不存在則根據相關配置創建池物件,并維護到Map容器中,然后從池中借用Redis連接物件;釋放物件時首先判斷物件所屬的池,將物件歸還到相應的池中,
最后總結,本文從物件池的一個簡單案例切入,主要分析common-pool2組件關于:池、工廠、配置、物件管理幾個角色的原始碼邏輯,并且參考其在Redis中的實踐,只是冰山一角,像這種通用型并且應用范圍廣的組件,很值得時常去讀一讀原始碼,真的令人驚嘆其鬼斧天工的設計,
五、參考原始碼
應用倉庫:
https://gitee.com/cicadasmile/butte-flyer-parent
組件封裝:
https://gitee.com/cicadasmile/butte-frame-parent
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/456966.html
標籤:Java
上一篇:面試官:MySQL 中的 varchar 最多能存盤多少個字符?大部分人都會答錯。。。
下一篇:動態代理
