java对象池commons-pool-1.6详解

java对象池commons-pool-1.6详解

精选文章moguli202025-03-04 11:26:568A+A-

对象的创建和销毁在一定程度上会消耗系统的资源,虽然jvm的性能在近几年已经得到了很大的提高,对于多数对象来说,没有必要利用对象池技术来进行对象的创建和管理。但是对于有些对象来说,其创建的代价还是比较昂贵的,比如线程、tcp连接、rpc连接、数据库连接等对象,因此对象池技术还是有其存在的意义。

Apache-commons-pool-1.6提供的对象池主要有两种: 一种是带Key的对象池 ,这种带Key的对象池是把相同的池对象放在同一个池中,也就是说有多少个key就有多少个池; 另一种是不带Key的对象池 ,这种对象池是把生产完全一致的对象放在同一个池中,但是有时候,单用对池内所有对象一视同仁的对象池,并不能解决的问题。例如:对于一组某些参数设置不同的同类对象——比如一堆指向不同地址的 java.net.URL对象或者一批代表不同语句的
java.sql.PreparedStatement对象,用这样的方法池化,就有可能取出不合用的对象。

1、对象池:

1)对象池接口介绍:

如果让我们去设计一个对象池接口,会给用户提供哪些核心的方法呢?

borrowObject(),returnObject()是两个核心方法,一个是’借’,一个是’还’。那么我们有可能需要对一个已经借到的对象置为失效(比如当我们的远程连接关闭或产生异常,这个连接不可用需要失效掉),invalidateObject()也是必不可少的。对象池刚刚创建的时候,我们可能需要预热一部分对象,而不是采用懒加载模式以避免系统启动时候的抖动,因此addObject()提供给用户,以进行对象池的预热。有创建就有销毁,clear()和close()就是用来清空对象池(觉得叫purge()可能更好一点)。除此之外,我们可能还需要一些简单的统计,比如getNumIdle()获得空闲对象个数和getNumActive()获得活动对象(被借出对象)的个数。如下表:

方法名

作用

borrowObject()

从池中借对象

returnObject()

还回池中

invalidateObject()

失效一个对象

addObject()

池中增加一个对象

clear()

清空对象池

close()

关闭对象池

getNumIdle()

获得空闲对象数量

getNumActive()

获得被借出对象数量

2)在commons-pool中有两类对象池接口(带key和不带key),一个是ObjectPool,另一个是KeyedObjectPool;此外,为了方便他们分别还对应了ObjectPoolFactory、KeyedObjectPoolFactory两个接口(这两个接口在功能上和他们都一样,只是使用形式上不一样)

3)对象池空间划分:

一个对象存储到对象池中,其位置不是一成不变的。空间的划分可以分为两种,一种是物理空间划分,一种是逻辑空间划分。不同的实现可能采用不同的技术手段,Commons Pool实际上采用了逻辑划分。如下图所示:

从整体上来讲,可以将空间分为池外空间和池内空间,池外空间是指被’出借’的对象所在的空间(逻辑空间)。池内空间进一步可以划分为 idle空间abandon空间invalid空间 。idle空间就是空闲对象所在的空间,空闲对象之间是有一定的组织结构的(详见后文)。abandon空间又被称作放逐空间,用于放逐被出借超时的对象。invalid空间其实就是对象的垃圾场,这些对象将不会在被使用,而是等待被gc处理掉。

4)对象池的放逐与驱逐策略:

下面我们会多次提到驱逐(eviction)和放逐(abandon),这两个概念是对象池设计的核心。

先来看驱逐,我们知道对象池的一个重要的特性就是伸缩性,所谓伸缩性是指对象池能够根据当前池中空闲对象的数量(maxIdle和minIdle配置)自动进行调整,进而避免内存的浪费。自动伸缩,这是驱逐所需要达到的目标,他是如何实现的呢?实际上在对象池内部,我们可以维护一个驱逐定时器(EvictionTimer),由
timeBetweenEvictionRunsMillis
参数对定时器的间隔加以控制,每次达到驱逐时间后,我们就选定一批对象(由 numTestsPerEvictionRun 参数进行控制)进行驱逐测试,这个测试可以采用策略模式,比如Commons Pool的DefaultEvictionPolicy,代码如下:

@Override
public boolean evict(EvictionConfig config, PooledObject underTest,
        int idleCount) {
    if ((config.getIdleSoftEvictTime() < underTest.getIdleTimeMillis() &&
            config.getMinIdle() < idleCount) ||
            config.getIdleEvictTime() < underTest.getIdleTimeMillis()) {
        return true;
    }
    return false;
}

对于符合驱逐条件的对象,将会被对象池无情的驱逐出空闲空间,并丢弃到invalid空间。之后对象池还需要保证内部空闲对象数量需要至少达到minIdle的控制要求。

我们在看来放逐,对象出借时间太长(由 removeAbandonedTimeout 控制),我们就把他们称作流浪对象,这些对象很有可能是那些用完不还的坏蛋们的杰作,也有可能是对象使用者出现了什么突发状况,比如网络连接超时时间设置长于放逐时间。总之,被放逐的对象是不允许再次回归到对象池中的,他们会被搁置到abandon空间,进而进入invalid空间再被gc掉以完成他们的使命。放逐由removeAbandoned()方法实现,分为标记过程和放逐过程,代码实现并不难,有兴趣的可以直接翻翻源代码。

驱逐是由内而外将对象驱逐出境,放逐则是由外而内,将对象流放。他们一内一外,正是整个对象池形成闭环的核心要素。

5)对象池有效性探测:

用过数据库连接池的同学可能对类似testOnBorrow的配置比较熟悉。除了testOnBorrow,对象池还提供了testOnCreate, testOnReturn, testWhileIdle,其中testWhileIdle是当对象处于空闲状态的时候所进行的测试,当测试通过则继续留在对象池中,如果失效,则弃置到invalid空间。所谓testOnBorrow其实就是当对象出借前进行测试,测试什么?当然是有效性测试,在测试之前我们需要调用factory.activateObject()以激活对象,在调用factory.validateObject(p)对准备出借的对象做有有效性检查,如果这个对象无效则可能有抛出异常的行为,或者返回空对象,这全看具体实现了。

testOnCreate表示当对象创建之后,再进行有效性测试,这并不适用于频繁创建和销毁对象的对象池,他与testOnBorrow的行为类似。testOnReturn是在对象还回到池子之前锁进行的测试,与出借的测试不同,testOnReturn无论是测试成功还是失败,我们都需要保证池子中的对象数量是符合配置要求的()ensureIdle()方法就是做这个事情),并且如果测试失败了,我们可以直接swallow这个异常,因为用户根本不需要关心池子的状态。

6)对象池的常见配置一览:

配置参数

意义

默认值

maxTotal

对象总数

8

maxIdle

最大空闲对象数

8

minIdle

最小空闲对象书

0

lifo

对象池借还是否采用lifo

true

fairness

对于借对象的线程阻塞恢复公平性

false

maxWaitMillis

借对象阻塞最大等待时间

-1

minEvictableIdleTimeMillis

最小驱逐空闲时间

30分钟

numTestsPerEvictionRun

每次驱逐数量

3

testOnCreate

创建后有效性测试

false

testOnBorrow

出借前有效性测试

false

testOnReturn

还回前有效性测试

false

testWhileIdle

空闲有效性测试

false

timeBetweenEvictionRunsMillis

驱逐定时器周期

false

blockWhenExhausted

对象池耗尽是否block

true

2、池化对象:

1)池化对象接口:( 被池化的对象需要实现该接口

池化对象就是对象池中所管理的基本单元。我们可以思考一下,如果直接将我们的原始对象放到对象池中是否可以?答案当然是可以,但是不好,因为如果那样做,我们的对象池就退化成了容器Collection了,之所以需要将原始对象wrapper成池对象,是因为我们需要提供额外的管理功能,比如生命周期管理。commons pool采用了PooledObject接口和KeyedPooledObject接口用于表达池对象,它主要抽象了池对象的状态管理和一些诸如状态变迁时所产生的统计指标,这些指标可以配合对象池做更精准的管理操作。

2)池化对象状态:

说到对池对象的管理,最重要的当属对状态的管理。对于状态管理,我们熟知的模型就是状态机模型了。池对象当然也有一套自己的状态机,我们先来看看commons pool所定义的池对象都有哪些状态:

状态

解释

IDLE

空闲状态

ALLOCATED

已出借状态

EVICTION

正在进行驱逐测试

EVICTION_RETURN_TO_HEAD

驱逐测试通过对象放回到头部

VALIDATION

空闲校验中

VALIDATION_PREALLOCATED

出借前校验中

VALIDATION_RETURN_TO_HEAD

校验通过后放回头部

INVALID

无效对象

ABANDONED

放逐中

RETURNING

换回对象池中

这里只需知道:放逐(

ABANDONED)指的是不在对象池中的对象超时流放;驱逐( EVICTION)指的是空闲对象超时销毁;VALIDATION是有效性校验,主要校验空闲对象的有效性。注意与驱逐和放逐之间的区别。我们通过一张图来看看状态之间的变迁。

我们看到上图的’圆圈’表示的就是池对象,其中中间的英文简写是其对应的状态。虚线外框则表示瞬时状态。比如RETURNING和ABANDONED。这里我们省略了VALIDATION_RETURN_TO_HEAD,VALIDATION_PREALLOCATED,EVICTION_RETURN_TO_HEAD,因为这对于我们理解池对象状态变迁并没有太多帮助。针对上图,我们重点关注四个方面:

  1. IDLE->ALLOCATED 即上图的borrow操作,除了需要将状态置为已分配,我们还需要考虑如果对象池耗尽了怎么办?是继续阻塞还是直接异常退出?如果阻塞是阻塞多久?
  2. ALLOCATED->IDLE 即上图的return操作,我们需要考虑的是,如果池对象还回到对象池,此时对象池空闲数已经达到上界或该对象已经无效,我们是否需要进行特殊处理?
  3. IDLE->EVICTION 与 ALLOCATED->ABANDONED 请参考后文
  4. IDLE->VALIDATION 是testWhileIdle的有效性测试所需要经历的状态变迁,他是指每隔一段时间对池中所有的idle对象进行有效性检查,以排除那些已经失效的对象。失效的对象将会弃置到invalid空间。

3)池化对象生命周期控制:

只搞清楚了池化对象的状态和状态转移是不够的,我们还应该能够对池对象生命周期施加影响。Commons Pool通过PooledObjectFactory接口和KeyedPooledObjectFactory对对象生命周期进行控制。该接口有如下方法:

方法

解释

makeObject

创建对象

destroyObject

销毁对象

validateObject

校验对象

activateObject

重新初始化对象

passivateObject

反初始化对象

我们需要注意,池对象必须经过创建(makeObject())和初始化过程(activateObject())后才能够被我们使用。我们看一看这些方法能够影响哪些状态变迁。

4)池对象组织结构:

池中的对象,并不是杂乱无章的,他们得有一定的组织结构。不同的组织结构可能会从整体影响对象池的使用。Apache Commons提供了两种组织结构,其一是有界阻塞双端队列(LinkedBlockingDeque),其二是key桶。

有界阻塞队列能够提供阻塞特性,当池中对象exhausted后,新申请对象的线程将会阻塞,这是典型的生产者/消费者模型,通过这种双端的阻塞队列,我们能够实现池对象的lifo或fifo。如下代码:

if (getLifo()) {
    idleObjects.addFirst(p);
} else {
    idleObjects.addLast(p);
}

因为是带有阻塞性质的队列,我们能够通过fairness参数控制线程获得锁的公平性,这里我们可以参考AQS实现,不说了。下面我们再来看一看key桶的数据结构:

从上图我们可以看出,每一个key对应一个的双端阻塞队列ObjectDeque,ObjectDeque实际上就是包装了LinkedBlockingDeque,采用这种结构我们能够对池对象进行一定的划分,从而更加灵活的使用对象池。Commons Pool采用了KeyedObjectPool用以表示采用这种数据结构的对象池。当我们borrow和return的时候,都需要指定对应的key空间。

参考:

?http://kriszhang.com/object-pool/? ?

对象池源码:
https://github.com/apache/commons-pool

对象池wiki:
https://commons.apache.org/proper/commons-pool/

我们详细介绍了commons-pool-1.6 java对象池的原理和基本概念,本文详细介绍不带key的对象池(ObjectPool)具体使用。

一、重点接口介绍:

在Apache-commons-pool-1.6中,对不带key的对象池定义了三个顶级接口:ObjectPool、ObjectPoolFactory、PoolableObjectFactory;其中ObjectPool和ObjectPoolFactory在功能上是一样的(都有三个不同的实现类),不同点仅在于使用上不同(后者通过工厂的方式使用),具体实例见后面。PoolableObjectFactory是池化对象接口,被池化的对象需要实现该接口。

1)对象池接口:

ObjectPool接口用于管理要被池化对象的借出(borrowObject)、归还(returnObject)、失效移除(invalidateObject)、预加载(addObject)以及对象池的清空(clear、close)等,它通过PoolableObjectFactory来完成相应对象的各种操作。ObjectPoolFactory接口是对ObjectPool接口的工厂化封装,用于生产ObjectPool接口。

2)池化对象接口:

PoolableObjectFactory用于管理被池化对象( 被池化的对象需要实现该接口 ),主要功能包括对象的产生(makeObject),激活(activateObject),挂起(passivateObject),检验(validateObject)和销毁(destroyObject)

二、对象池使用流程:

1)使用者通过对象池(
ObjectPool/ObjectPoolFactory)接口实现类中的borrowObject方法来从对象池中获取对象,并将active标记+1;

  1. 如果池中有idle的对象(idle>0),直接返回该对象,并将idle-1;
  2. 如果池中没有idle的对象,调用池化对象接口实现类中的makeObject方法创建对象;如果目前对象池的active数达到maxactive,那么阻塞等待;
  3. 如果配置了testonborrow=ture,那么在获取对象之前还会调用池化对象接口PoolableObjectFactory的实现类中validateObject方法来检验对象的有效性,如果非法则直接调用PoolableObjectFactory接口的实现类中destroyObject方法销毁;

2)使用完对象必须要归还给对象池,通过对象池(
ObjectPool/ObjectPoolFactory)接口的实现类中的returnObject方法将对象归还到对象池中,并将active标记-1;

  1. 如果配置了testonreturn=ture,那么在归还对象前还会调用池化对象接口PoolableObjectFactory的实现类中validateObject方法来检验对象的有效性,如果非法则直接调用PoolableObjectFactory接口的实现类中destroyObject方法销毁;
  2. 如果idle数大于maxidle,则销毁对象,否则唤醒上面阻塞的进程或者放到idle区(idle+1);
  3. 如果配置了removeAbandonedTimeout,则超过该事件的池化对象直接进入放逐区;

3)驱逐检验:如果配置了
timeBetweenEvictionRunsMillis,为了保证对象池的伸缩性(避免浪费资源)对象池会对idle状态的池化对象进行驱逐(
minEvictableIdleTimeMillis控制驱逐空闲时间);

注:对象池只有在borrowObject的时候才回去真正创建对象(调用makeObject或者从池中的idle队列中获取),如果要预申请对象,需要调用吃对象接口ObjectPool的addObject方法。

三、对象池实现类:

ObjectPool接口下面有三个实现类:GenericObjectPool、StackObjectPool、SoftReferenceObjectPool。

1、genericObjectpool:(重点)

GenericObjectPool采用LIFO/FIF,池的默认行为是一个LIFO,这就意味着,当池中有空闲可用的对象时,调用borrowObject方法会返回最近(“后进”)的实例。如果LIFO策略在池中是false的,实例的返回按相反的顺序,-先进 - 先出。它利用一个
org.apache.commons.collections.CursorableLinkedList对象来保存对象池里的对象。

1)这种对象池的特色是:

  • 可以设定最多能从池中借出多少个对象。
  • 可以设定池中最多能保存多少个对象。
  • 可以设定在池中已无对象可借的情况下,调用它的borrowObject方法时的行为,是等待、创建新的实例还是抛出异常。
  • 可以分别设定对象借出和还回时,是否进行有效性检查。
  • 可以设定是否使用一个单独的线程,对池内对象进行后台清理。

2)构造函数:

GenericObjectPool一共7个构造函数,最简单的是GenericObjectPool(PoolableObjectFactory factory),仅仅指明要用的PoolableObjectFactory实例,其它参数则采用默认值;此外可以通过GenericObjectPool(PoolableObjectFactory factory, GenericObjectPool.Config config) 构造函数来构造复杂的配置,其中GenericObjectPool.Config可以设置:

  • int maxActive
  • int maxIdle
  • long maxWait
  • long minEvictableIdleTimeMillis
  • int numTestsPerEvictionRun
  • boolean testOnBorrow
  • boolean testOnReturn
  • boolean testWhileIdle
  • long timeBetweenEvictionRunsMillis
  • byte whenExhaustedAction

2、StackObjectPool:

StackObjectPool采用LIFO,利用一个java.util.Stack对象来保存对象池里的对象。

1)这种对象池的特色是:

  1. 可以为对象池指定一个初始的参考大小(当空间不够时会自动增长)。
  2. 在对象池已空的时候,调用它的borrowObject方法,会自动返回新创建的实例。
  3. 可以为对象池指定一个可保存的对象数目的上限。达到这个上限之后,再向池里送回的对象会被自动送去回收。

2)构造函数:

StackObjectPool一共6个构造函数,最简单的一个是StackObjectPool(),一切采用默认的设置,也不指明要用的PoolableObjectFactory实例;最复杂的一个则是StackObjectPool(PoolableObjectFactory factory, int max, int init),其中参数factory指明要与之配合使用的PoolableObjectFactory实例、参数max设定可保存对象数目的上限、参数init则指明初始的参考大小。

3、SoftReferenceObjectPool:

SoftReferenceObjectPool采用LIFO,这种池额外功能是包装了每一个引用对象,允许GC根据内存需求清除这些对象。利用一个java.util.ArrayList对象来保存对象池里的对象。不过它并不在对象池里直接保存对象本身,而是保存它们的“软引用”(Soft Reference)。

1)这种对象池的特色是:

  1. 可以保存任意多个对象,不会有容量已满的情况发生。
  2. 在对象池已空的时候,调用它的borrowObject方法,会自动返回新创建的实例。
  3. 可以在初始化同时,在池内预先创建一定量的对象。
  4. 当内存不足的时候,池中的对象可以被Java虚拟机回收。

2)构造方法:

SoftReferenceObjectPool有3个构造方法,最简单的是SoftReferenceObjectPool(),不预先在池内创建对象,也不指明要用的PoolableObjectFactory实例;最复杂的一个则是SoftReferenceObjectPool(PoolableObjectFactory factory, int initSize)。其中:参数factory指明要与之配合使用的PoolableObjectFactory实例、参数initSize则指明初始化时在池中创建多少个对象

四、实例:

1)定义一个被池化的对象:

package cn.eud.nuc.pool.bean;

public class MyBean {
  private Long id;
  private String name;
  private String other;
  
  public void abc(int inxtInt) throws InterruptedException {
    Thread.sleep(1000);
    System.out.println(this.toString());
  }
  
  public MyBean(Long id, String name, String other) {
    super();
    this.id = id;
    this.name = name;
    this.other = other;
  }
  public Long getId() {
    return id;
  }
  public void setId(Long id) {
    this.id = id;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public String getOther() {
    return other;
  }
  public void setOther(String other) {
    this.other = other;
  }
  
  @Override
  public String toString() {
    return "MyBean [id=" + id + ", name=" + name + ", other=" + other + "]";
  }
  
}

2)实现池化接口:

package cn.eud.nuc.pool.bean;

import java.util.concurrent.ThreadLocalRandom;

import org.apache.commons.pool.BasePoolableObjectFactory;

public class MyBeanPool extends BasePoolableObjectFactory{
  private ThreadLocalRandom random = ThreadLocalRandom.current();

  @Override
  public MyBean makeObject() throws Exception {
    System.out.println("make...");
    long id = random.nextLong(10000000);
    return new MyBean(id,System.currentTimeMillis()+"_test","");
  }
  
  @Override
  public void destroyObject(MyBean client) throws Exception {
    System.out.println("======================================================destroy...");
  }


  @Override
  public boolean validateObject(MyBean client) {
    return true;
  }

}

3)测试类1:(使用

GenericObjectPool接口)

package cn.eud.nuc.pool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom;

import org.apache.commons.pool.impl.GenericObjectPool;

import cn.eud.nuc.pool.bean.MyBean;
import cn.eud.nuc.pool.bean.MyBeanPool;

public class Test {
  public static void main(String...strings) throws Exception {
    MyBeanPool myBeanPool = new MyBeanPool();
    
    GenericObjectPool.Config poolConfig = new GenericObjectPool.Config();
    poolConfig.maxActive = 100;
    poolConfig.maxIdle = 1;
    //poolConfig.minIdle = 0;
    poolConfig.minEvictableIdleTimeMillis = 1000000000;
    poolConfig.timeBetweenEvictionRunsMillis = 10 * 2L;
    poolConfig.testOnBorrow=false;
    poolConfig.testOnReturn=true;
    poolConfig.testWhileIdle=true;
    poolConfig.lifo = false;
    //poolConfig.maxWait = 100000000;
    GenericObjectPool genericObjectPool = new GenericObjectPool(myBeanPool, poolConfig);
    genericObjectPool.addObject();
    genericObjectPool.addObject();
    genericObjectPool.addObject();
    genericObjectPool.addObject();
    ExecutorService pool = Executors.newFixedThreadPool(200);
    for (int i=0;i<5000;i++) {
      pool.submit(new MyThread(genericObjectPool));
    }
  }
}

class MyThread implements Runnable {
  private ThreadLocalRandom random = ThreadLocalRandom.current();

  private GenericObjectPool genericObjectPool;
  
  public MyThread(GenericObjectPool genericObjectPool) {
    super();
    this.genericObjectPool = genericObjectPool;
  }

  @Override
  public void run() {
    int nextInt = random.nextInt(100);
    MyBean borrowObject = null;
    boolean flag = true;
    try {
      
      borrowObject = genericObjectPool.borrowObject();
      borrowObject.abc(nextInt);
    } catch (Exception e) {
      flag = false;
      e.printStackTrace();
    } finally {
      try {
        if (flag) {
          genericObjectPool.returnObject(borrowObject);
        } else {
          genericObjectPool.invalidateObject(borrowObject);
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }
}

4)测试类2(使用GenericObjectPoolFactory接口)

package cn.eud.nuc.pool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom;
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.apache.commons.pool.impl.GenericObjectPoolFactory;
import cn.eud.nuc.pool.bean.MyBean;
import cn.eud.nuc.pool.bean.MyBeanPool;

public class Test2 {
  public static void main(String...strings) throws InterruptedException {
    MyBeanPool myBeanPool = new MyBeanPool();
    
    GenericObjectPool.Config poolConfig = new GenericObjectPool.Config();
    poolConfig.maxActive = 100;
    poolConfig.maxIdle = 1;
    //poolConfig.minIdle = 0;
    poolConfig.minEvictableIdleTimeMillis = 1000000000;
    poolConfig.timeBetweenEvictionRunsMillis = 10 * 2L;
    poolConfig.testOnBorrow=false;
    poolConfig.testOnReturn=true;
    poolConfig.testWhileIdle=true;
    poolConfig.lifo = false;
    //poolConfig.maxWait = 100000000;
    GenericObjectPoolFactory genericObjectPoolFactory = new GenericObjectPoolFactory<>(myBeanPool, poolConfig);
    ObjectPool createPool = genericObjectPoolFactory.createPool();
    
    ExecutorService pool = Executors.newFixedThreadPool(200);
    for (int i=0;i<5000;i++) {
      pool.submit(new MyThread1(createPool));
    }
  }
}

class MyThread1 implements Runnable {
  private ThreadLocalRandom random = ThreadLocalRandom.current();

  private ObjectPool genericObjectPool;
  
  public MyThread1(ObjectPool genericObjectPool) {
    super();
    this.genericObjectPool = genericObjectPool;
  }

  @Override
  public void run() {
    int nextInt = random.nextInt(100);
    MyBean borrowObject = null;
    boolean flag = true;
    try {
      
      borrowObject = genericObjectPool.borrowObject();
      borrowObject.abc(nextInt);
    } catch (Exception e) {
      flag = false;
      e.printStackTrace();
    } finally {
      try {
        if (flag) {
          genericObjectPool.returnObject(borrowObject);
        } else {
          genericObjectPool.invalidateObject(borrowObject);
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }
}
点击这里复制本文地址 以上内容由莫古技术网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

莫古技术网 © All Rights Reserved.  滇ICP备2024046894号-2