基于Apache組件分析對象池原理的實現(xiàn)案例分析
池塘里養(yǎng):Object;
一、設(shè)計與原理
1、基礎(chǔ)案例
首先看一個基于common-pool2對象池組件的應(yīng)用案例,主要有工廠類、對象池、對象三個核心角色,以及池化對象的使用流程:
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 {
// 創(chuàng)建對象
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<>());
}
}
案例中對象是完全自定義的;對象工廠中則重寫兩個核心方法:創(chuàng)建和包裝,以此創(chuàng)建池化對象;對象池的構(gòu)建依賴定義的對象工廠,配置采用組件提供的常規(guī)配置類;可以通過調(diào)整對象實例化的時間以及創(chuàng)建對象的個數(shù),初步理解對象池的原理。
2、接口設(shè)計
1.1 PooledObjectFactory 接口
- 工廠類,負責對象實例化,創(chuàng)建、驗證、銷毀、狀態(tài)管理等;
- 案例中
BasePooledObjectFactory類則是該接口的基礎(chǔ)實現(xiàn);
1.2 ObjectPool 接口
- 對象池,并且繼承
Closeable接口,管理對象生命周期,以及活躍和空閑對象的數(shù)據(jù)信息獲??; - 案例中
GenericObjectPool類是對于該接口的實現(xiàn),并且是可配置化的方式;
1.3 PooledObject 接口
- 池化對象,基于包裝類被維護在對象池中,并且維護一些附加信息用來跟蹤,例如時間、狀態(tài);
- 案例中采用
DefaultPooledObject包裝類,實現(xiàn)該接口并且線程安全,注意工廠類中的重寫;
3、運行原理

通過對象池獲取對象,可能是通過工廠新創(chuàng)建的,也可能是空閑的對象;當對象獲取成功且使用完成后,需要歸還對象;在案例執(zhí)行過程中,不斷查詢對象池中空閑和活躍對象的數(shù)量,用來監(jiān)控池的變化。
二、構(gòu)造分析
1、對象池
public GenericObjectPool(final PooledObjectFactory<T> factory,final GenericObjectPoolConfig<T> config);
在完整的構(gòu)造方法中,涉及到三個核心對象:工廠對象、配置對象、雙端阻塞隊列;通過這幾個對象創(chuàng)建一個新的對象池;在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支持在隊列的首尾操作元素,例如添加和移除等;操作需要通過主鎖進行加鎖,并且基于兩個狀態(tài)鎖進行協(xié)作;
// 隊首節(jié)點 private transient LinkedBlockingDeque.Node<E> first; // 隊尾節(jié)點 private transient LinkedBlockingDeque.Node<E> last; // 主鎖 private final InterruptibleReentrantLock lock; // 非空狀態(tài)鎖 private final Condition notEmpty; // 未滿狀態(tài)鎖 private final Condition notFull;
關(guān)于鏈表和隊列的特點,在之前的文章中有單獨分析過,此處的源碼在JDK的容器中也很常見,這里不在贅述,對象池的整個構(gòu)造有大致輪廓之后,下面再來細看對象的管理邏輯。
三、對象管理
1、添加對象
創(chuàng)建一個新對象并且放入池中,通常應(yīng)用在需要預加載的場景中;涉及到兩個核心操作:工廠創(chuàng)建對象,對象池化管理;
public void GenericObjectPool.addObject() throws Exception ;
2、借用對象
public T GenericObjectPool.borrowObject(final long borrowMaxWaitMillis) throws Exception ;

首先從隊列中獲取對象;如果沒有獲取到,調(diào)用工廠創(chuàng)建方法,之后池化管理;對象獲取之后會改變狀態(tài)為ALLOCATED使用中;最后經(jīng)過工廠的確認,完成對象獲取動作;
3、歸還對象
public void GenericObjectPool.returnObject(final T obj) ;

歸還對象的時候,首先轉(zhuǎn)換為池化對象和標記RETURNING狀態(tài);經(jīng)過多次校驗判斷,如果失敗則銷毀該對象,并重新維護對象池中可用的空閑對象;最終對象被標記為空閑狀態(tài),如果不超出最大空閑數(shù),則對象被放到隊列的某一端;
4、對象狀態(tài)
關(guān)于池化對象的狀態(tài)在PooledObjectState類中有枚舉和描述,在圖中只是對部分幾個狀態(tài)流轉(zhuǎn)做示意,更多細節(jié)可以參考狀態(tài)類;

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

2.1 配置轉(zhuǎn)換
// 連接配置
class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
private static class PoolBuilderFactory {
// 構(gòu)建對象池配置
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的相關(guān)參數(shù),構(gòu)建到GenericObjectPoolConfig類中,即配置加載過程;
2.2 對象池構(gòu)造
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);
}
}在構(gòu)造方法中獲取對象池的配置信息,這里并沒有直接實例化池對象,而是采用ConcurrentHashMap容器來動態(tài)維護;
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);
}
}在獲取池對象時,如果不存在則根據(jù)相關(guān)配置創(chuàng)建池對象,并維護到Map容器中,然后從池中借用Redis連接對象;釋放對象時首先判斷對象所屬的池,將對象歸還到相應(yīng)的池中。
最后總結(jié),本文從對象池的一個簡單案例切入,主要分析common-pool2組件關(guān)于:池、工廠、配置、對象管理幾個角色的源碼邏輯,并且參考其在Redis中的實踐,只是冰山一角,像這種通用型并且應(yīng)用范圍廣的組件,很值得時常去讀一讀源碼,真的令人驚嘆其鬼斧天工的設(shè)計。
五、參考源碼
應(yīng)用倉庫:
https://gitee.com/cicadasmile/butte-flyer-parent
組件封裝:
https://gitee.com/cicadasmile/butte-frame-parent
到此這篇關(guān)于基于Apache組件分析對象池原理的文章就介紹到這了,更多相關(guān)Apache組件分析對象池原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Component和Configuration注解區(qū)別實例詳解
這篇文章主要為大家介紹了Component和Configuration注解區(qū)別實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11
Java如何實現(xiàn)Unicode和中文相互轉(zhuǎn)換
這篇文章主要介紹了Java如何實現(xiàn)Unicode和中文相互轉(zhuǎn)換問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01
SpringBoot優(yōu)化連接數(shù)的方法詳解
SpringBoot開發(fā)最大的好處是簡化配置,內(nèi)置了Tomcat,下面這篇文章主要給大家介紹了關(guān)于SpringBoot優(yōu)化連接數(shù)的相關(guān)資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2023-06-06
Spring Cloud Feign請求添加headers的實現(xiàn)方式
這篇文章主要介紹了Spring Cloud Feign請求添加headers的實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04

