设为首页 收藏本站
查看: 719|回复: 0

[经验分享] Apache commons-pool对象池原理分析

[复制链接]

尚未签到

发表于 2017-1-7 08:03:32 | 显示全部楼层 |阅读模式
   Apache commons-pool本质上是"对象池",即通过一定的规则来维护对象集合的容器;commos-pool在很多场景中,用来实现"连接池"/"任务worker池"等,大家常用的dbcp数据库连接池,也是基于commons-pool实现.
    commons-pool实现思想非常简单,它主要的作用就是将"对象集合"池化,任何通过pool进行对象存取的操作,都会严格按照"pool配置"(比如池的大小)实时的创建对象/阻塞控制/销毁对象等.它在一定程度上,实现了对象集合的管理以及对象的分发.
    1) 将创建对象的方式,使用工厂模式;
    2) 通过"pool配置"来约束对象存取的时机
    3) 将对象列表保存在队列中(LinkedList)
 
    首选需要声明,不同的"对象池"(或者连接池)在设计上可能存在很大的区别,但是在思想上大同小异,本文主要讲解commons-pool,它和其他"连接池"的区别在此不多讨论.
 
一.对象生命周期

DSC0000.jpg
 

 
二.Config详解:


  • maxActive: 链接池中最大连接数,默认为8.
  • maxIdle: 链接池中最大空闲的连接数,默认为8.
  • minIdle: 连接池中最少空闲的连接数,默认为0.
  • maxWait: 当连接池资源耗尽时,调用者最大阻塞的时间,超时将跑出异常。单位,毫秒数;默认为-1.表示永不超时.
  • minEvictableIdleTimeMillis: 连接空闲的最小时间,达到此值后空闲连接将可能会被移除。负值(-1)表示不移除。
  • softMinEvictableIdleTimeMillis: 连接空闲的最小时间,达到此值后空闲链接将会被移除,且保留“minIdle”个空闲连接数。默认为-1.
  • numTestsPerEvictionRun: 对于“空闲链接”检测线程而言,每次检测的链接资源的个数。默认为3.
  • testOnBorrow: 向调用者输出“链接”资源时,是否检测是有有效,如果无效则从连接池中移除,并尝试获取继续获取。默认为false。建议保持默认值.
  • testOnReturn:  向连接池“归还”链接时,是否检测“链接”对象的有效性。默认为false。建议保持默认值.
  • testWhileIdle:  向调用者输出“链接”对象时,是否检测它的空闲超时;默认为false。如果“链接”空闲超时,将会被移除。建议保持默认值.
  • timeBetweenEvictionRunsMillis:  “空闲链接”检测线程,检测的周期,毫秒数。如果为负值,表示不运行“检测线程”。默认为-1.
  •  whenExhaustedAction: 当“连接池”中active数量达到阀值时,即“链接”资源耗尽时,连接池需要采取的手段, 默认为1:
     -> 0 : 抛出异常,
     -> 1 : 阻塞,直到有可用链接资源
     -> 2 : 强制创建新的链接资源

  这些属性均可以在org.apache.commons.pool.impl.GenericObjectPool.Config中进行设定。
  三.原理解析
  1) 对象池创建(参考GenericObjectPool):



  • public GenericObjectPool(PoolableObjectFactory factory, GenericObjectPool.Config config) :此方法创建一个GenericObjectPool实例,GenericObjectPool类已经实现了和对象池有关的所有核心操作,开发者可以通过继承或者封装的方式来使用它.通过此构造函数,我们能够清晰的看到,一个Pool中需要指定PoolableObjectFactory 实例,以及此对象池的Config信息.PoolableObjectFactory主要用来"创建新对象",比如当对象池中的对象不足时,可以使用PoolableObjectFactory.makeObject()方法来创建对象,并交付给Pool管理.
  此构造函数实例化了一个LinkedList作为"对象池"容器,用来存取"对象".此外还会根据timeBetweenEvictionRunsMillis的值来决定是否启动一个后台线程,此线程用来周期性扫描pool中的对象列表,已检测"对象池中的对象"空闲(idle)的时间是否达到了阀值,如果是,则移除此对象.

if ((getMinEvictableIdleTimeMillis() > 0) &&
(idleTimeMilis > getMinEvictableIdleTimeMillis())) {
removeObject = true;
}
...
if (removeObject) {
try {
_factory.destroyObject(pair.value);
} catch(Exception e) {
// ignored
}
}
  2) 对象工厂PoolableObjectFactory接口:
  commons-pool通过使用ObjectFactory(工厂模式)的方式将"对象池中的对象"的创建/检测/销毁等特性解耦出来,这是一个非常良好的设计思想.此接口有一个抽象类BasePoolableObjectFactory,可供开发者继承和实现.



  • Object makeObject() : 创建一个新对象;当对象池中的对象个数不足时,将会使用此方法来"输出"一个新的"对象",并交付给对象池管理.

  • void destroyObject(Object obj) : 销毁对象,如果对象池中检测到某个"对象"idle的时间超时,或者操作者向对象池"归还对象"时检测到"对象"已经无效,那么此时将会导致"对象销毁";"销毁对象"的操作设计相差甚远,但是必须明确:当调用此方法时,"对象"的生命周期必须结束.如果object是线程,那么此时线程必须退出;如果object是socket操作,那么此时socket必须关闭;如果object是文件流操作,那么此时"数据flush"且正常关闭.

  • boolean validateObject(Object obj) : 检测对象是否"有效";Pool中不能保存无效的"对象",因此"后台检测线程"会周期性的检测Pool中"对象"的有效性,如果对象无效则会导致此对象从Pool中移除,并destroy;此外在调用者从Pool获取一个"对象"时,也会检测"对象"的有效性,确保不能讲"无效"的对象输出给调用者;当调用者使用完毕将"对象归还"到Pool时,仍然会检测对象的有效性.所谓有效性,就是此"对象"的状态是否符合预期,是否可以对调用者直接使用;如果对象是Socket,那么它的有效性就是socket的通道是否畅通/阻塞是否超时等.

  • void activateObject(Object obj) : "激活"对象,当Pool中决定移除一个对象交付给调用者时额外的"激活"操作,比如可以在activateObject方法中"重置"参数列表让调用者使用时感觉像一个"新创建"的对象一样;如果object是一个线程,可以在"激活"操作中重置"线程中断标记",或者让线程从阻塞中唤醒等;如果object是一个socket,那么可以在"激活操作"中刷新通道,或者对socket进行链接重建(假如socket意外关闭)等.

  • void void passivateObject(Object obj) : "钝化"对象,当调用者"归还对象"时,Pool将会"钝化对象";钝化的言外之意,就是此"对象"暂且需要"休息"一下.如果object是一个socket,那么可以passivateObject中清除buffer,将socket阻塞;如果object是一个线程,可以在"钝化"操作中将线程sleep或者将线程中的某个对象wait.需要注意的时,activateObject和passivateObject两个方法需要对应,避免死锁或者"对象"状态的混乱.
  3) ObjectPool接口与实现:
  对象池是commons-pool的核心接口,用来维护"对象列表"的存取;其中GenericObjectPool是其实现类,它已经实现了相关的功能.



  • Object borrowObject() : 从Pool获取一个对象,此操作将导致一个"对象"从Pool移除(脱离Pool管理),调用者可以在获得"对象"引用后即可使用,且需要在使用结束后"归还".如下为伪代码:

public Object borrowObject() throws Exception {
Object value = null;
synchronized (this) {
if(!_pool.isEmpty()){
value = _pool.remove();
}
}
for(;;) {     
//如果Pool中没有"对象",则根据相应的"耗尽"策略
if(value == null) {
switch(whenExhaustedAction) {
//如果耗尽,仍继续创建新"对象"
case WHEN_EXHAUSTED_GROW:
value = _factory.makeObject();
break;
//如果耗尽,则终止,此时以异常的方式退出.
case WHEN_EXHAUSTED_FAIL:
throw new NoSuchElementException("Pool exhausted");
//如果耗尽,则阻塞,直到有"对象"归还
case WHEN_EXHAUSTED_BLOCK:
try {
synchronized (value) {
if (value == null) {
//maxWait为Config中指定的"最大等待时间"
if(maxWait <= 0) {
latch.wait();
} else {
latch.wait(waitTime);
}
} else {
break;
}
}
} catch(InterruptedException e) {
//
break;
}
default://
}
}
try {
_factory.activateObject(latch.getPair().value);
if(_testOnBorrow &&
!_factory.validateObject(latch.getPair().value)) {
throw new Exception("ValidateObject failed");
}
return value;
}
catch (Throwable e) {
try {
_factory.destroyObject(latch.getPair().value);
} catch (Throwable e2) {
//
}
}
}
}



  • void returnObject(Object obj) : "归还"对象,当"对象"使用结束后,需要归还到Pool中,才能维持Pool中对象的数量可控,如果不归还到Pool,那么将意味着在Pool之外,将有大量的"对象"存在,那么就使用了"对象池"的意义.如下为伪代码:

public void returnObject(Object obj) throws Exception {
try {
boolean success = true;//
if(_testOnReturn && !(_factory.validateObject(obj))) {
success = false;
} else {
_factory.passivateObject(obj);
}
synchronized (this) {
//检测pool中已经空闲的对象个数是否达到阀值.
if((_maxIdle >= 0) && (_pool.size() >= _maxIdle)) {
success = false;
} else if(success) {
_pool.addFirst(new ObjectTimestampPair(obj));
}
}
// Destroy the instance if necessary
if(!success) {
try {
_factory.destroyObject(obj);
} catch(Exception e) {
// ignored
}
}
} catch (Exception e) {
//
}
}


  • void invalidateObject(Object obj) : 销毁对象,直接调用ObjectFactory.destroyObject(obj);.

  • void addObject() : 开发者可以直接调用addObject方法用于直接创建一个"对象"并添加到Pool中.
  四.代码实例.
  本实例主要用来演示一个"TCP连接池".
  1) ConnectionPoolFactory.java:

import org.apache.commons.pool.BasePoolableObjectFactory;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.apache.commons.pool.impl.GenericObjectPool.Config;
public class ConnectionPoolFactory {
private GenericObjectPool pool;
public ConnectionPoolFactory(Config config,String ip,int port){
ConnectionFactory factory = new ConnectionFactory(ip, port);
pool = new GenericObjectPool(factory, config);
}
public Socket getConnection() throws Exception{
return (Socket)pool.borrowObject();
}
public void releaseConnection(Socket socket){
try{
pool.returnObject(socket);
}catch(Exception e){
if(socket != null){
try{
socket.close();
}catch(Exception ex){
//
}
}
}
}
/**
* inner
* @author qing
*
*/
class ConnectionFactory extends BasePoolableObjectFactory {
private InetSocketAddress address;
public ConnectionFactory(String ip,int port){
address = new InetSocketAddress(ip, port);
}
@Override
public Object makeObject() throws Exception {
Socket socket = new Socket();
socket.connect(address);
return socket;
}
public void destroyObject(Object obj) throws Exception  {
if(obj instanceof Socket){
((Socket)obj).close();
}
}
public boolean validateObject(Object obj) {
if(obj instanceof Socket){
Socket socket = ((Socket)obj);
if(!socket.isConnected()){
return false;
}
if(socket.isClosed()){
return false;
}
return true;
}
return false;
}

}
}

  2) TestMain.java(测试类):

public class TestMain {
/**
* @param args
*/
public static void main(String[] args) {
Config config = new Config();
config.maxActive = 16;
config.maxWait = 30000;
ConnectionPoolFactory poolFactory = new ConnectionPoolFactory(config, "127.0.0.1", 8011);
Socket socket = null ;
try{
socket = poolFactory.getConnection();
////
}catch(Exception e){
e.printStackTrace();
}finally{
if(socket != null){
poolFactory.releaseConnection(socket);
}
}
}
}
  ---END--

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-324862-1-1.html 上篇帖子: Apache Commons 项目运用 下篇帖子: Apache Commons Pipeline 使用学习(一)
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表