Backend Development 17 min read

Understanding Java Connection Pools: Commons Pool 2, Jedis, and HikariCP

This article explains the principles of object pooling in Java, introduces the Commons Pool 2 library, demonstrates its use with Redis client Jedis, compares performance with JMH benchmarks, and details the configuration and advantages of the high‑performance HikariCP database connection pool.

Architect's Tech Stack
Architect's Tech Stack
Architect's Tech Stack
Understanding Java Connection Pools: Commons Pool 2, Jedis, and HikariCP

大家好,现在介绍一款非常强大,高效,并且号称“史上最快连接池”。由此可见他是有多受人喜欢,并且

在SpringBoot2.0之后,采用的默认数据库连接池就是Hikari 。

我们知道的连接池有C3P0,DBCP,它们都比较成熟稳定,但性能不是十分好。所以有了BoneCP这个连接池,它是一个高速、免费、开源的JAVA连接池,它的性能几乎是C3P0、DBCP的25倍,十分强悍。

在我们平常的编码中,通常会将一些对象保存起来,这主要考虑的是对象的创建成本。

比如像线程资源、数据库连接资源或者 TCP 连接等,这类对象的初始化通常要花费比较长的时间,如果频繁地申请和销毁,就会耗费大量的系统资源,造成不必要的性能损失。

并且这些对象都有一个显著的特征,就是通过轻量级的重置工作,可以循环、重复地使用。

这个时候,我们就可以使用一个虚拟的池子,将这些资源保存起来,当使用的时候,我们就从池子里快速获取一个即可。

在Java 中,池化技术应用非常广泛,常见的就有数据库连接池、线程池等,本文主讲连接池,线程池我们将在后续的博客中进行介绍。

公用池化包 Commons Pool 2

我们首先来看一下 Java 中公用的池化包 Commons Pool 2,来了解一下对象池的一般结构。

根据我们的业务需求,使用这套 API 能够很容易实现对象的池化管理。

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.11.1</version>
</dependency>

GenericObjectPool 是对象池的核心类,通过传入一个对象池的配置和一个对象的工厂,即可快速创建对象池。

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

案例

Redis 的常用客户端 Jedis,就是使用 Commons Pool 管理连接池的,可以说是一个最佳实践。下图是 Jedis 使用工厂创建对象的主要代码块。

对象工厂类最主要的方法就是makeObject,它的返回值是 PooledObject 类型,可以将对象使用 new DefaultPooledObject<>(obj) 进行简单包装返回。

redis.clients.jedis.JedisFactory,使用工厂创建对象。

@Override
public PooledObject<Jedis> makeObject() throws Exception {
  Jedis jedis = null;
  try {
    jedis = new Jedis(jedisSocketFactory, clientConfig);
    //主要的耗时操作
    jedis.connect();
    //返回包装对象
    return new DefaultPooledObject<>(jedis);
  } catch (JedisException je) {
    if (jedis != null) {
      try { jedis.quit(); } catch (RuntimeException e) { logger.warn("Error while QUIT", e); }
      try { jedis.close(); } catch (RuntimeException e) { logger.warn("Error while close", e); }
    }
    throw je;
  }
}

我们再来介绍一下对象的生成过程,如下图,对象在进行获取时,将首先尝试从对象池里拿出一个,如果对象池中没有空闲的对象,就使用工厂类提供的方法,生成一个新的。

public T borrowObject(final Duration borrowMaxWaitDuration) throws Exception {
    //此处省略若干行
    while (p == null) {
        create = false;
        //首先尝试从池子中获取。
        p = idleObjects.pollFirst();
        // 池子里获取不到,才调用工厂内生成新实例
        if (p == null) {
            p = create();
            if (p != null) { create = true; }
        }
        //此处省略若干行
    }
    //此处省略若干行
}

那对象是存在什么地方的呢?这个存储的职责,就是由一个叫作 LinkedBlockingDeque 的结构来承担的,它是一个双向的队列。

接下来看一下 GenericObjectPoolConfig 的主要属性:

// GenericObjectPoolConfig本身的属性
private int maxTotal = DEFAULT_MAX_TOTAL;
private int maxIdle = DEFAULT_MAX_IDLE;
private int minIdle = DEFAULT_MIN_IDLE;
// 其父类BaseObjectPoolConfig的属性
private boolean lifo = DEFAULT_LIFO;
private boolean fairness = DEFAULT_FAIRNESS;
private long maxWaitMillis = DEFAULT_MAX_WAIT_MILLIS;
private long minEvictableIdleTimeMillis = DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
private long evictorShutdownTimeoutMillis = DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT_MILLIS;
private long softMinEvictableIdleTimeMillis = DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
private int numTestsPerEvictionRun = DEFAULT_NUM_TESTS_PER_EVICTION_RUN;
private EvictionPolicy
evictionPolicy = null; 
private String evictionPolicyClassName = DEFAULT_EVICTION_POLICY_CLASS_NAME;
private boolean testOnCreate = DEFAULT_TEST_ON_CREATE;
private boolean testOnBorrow = DEFAULT_TEST_ON_BORROW;
private boolean testOnReturn = DEFAULT_TEST_ON_RETURN;
private boolean testWhileIdle = DEFAULT_TEST_WHILE_IDLE;
private long timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
private boolean blockWhenExhausted = DEFAULT_BLOCK_WHEN_EXHAUSTED;

参数很多,要想了解参数的意义,我们首先来看一下一个池化对象在整个池子中的生命周期。

对象池在进行初始化时,要指定三个主要的参数:

maxTotal 对象池中管理的对象上限

maxIdle 最大空闲数

minIdle 最小空闲数

其中 maxTotal 和业务线程有关,当业务线程想要获取对象时,会首先检测是否有空闲的对象。如果有,则返回一个;否则进入创建逻辑。如果池中个数已经达到了最大值,就会创建失败,返回空对象。

对象在获取的时候,有一个非常重要的参数——最大等待时间(maxWaitMillis),默认 -1 表示永不超时,直到有对象空闲。

如果对象创建非常缓慢或使用非常繁忙,业务线程会持续阻塞(blockWhenExhausted 默认为 true),导致正常服务也不能运行。

面试题

一般面试官会问:你会把超时参数设置成多大呢?我一般会把最大等待时间设置成接口可以忍受的最大延迟,例如 500~1000ms。

超时之后,会抛出 NoSuchElementException 异常,请求会快速失败,这种 Fail Fast 思想在互联网应用非常广泛。

带有 evcit 字样的参数主要处理对象逐出,池化对象在运行时也会占用系统资源,需要适时回收。

四个 test 参数(testOnCreate、testOnBorrow、testOnReturn、testWhileIdle)决定了在创建、获取、归还、空闲检测时是否进行有效性检测,默认 false,生产环境建议仅开启 testWhileIdle 并调节检测间隔。

JMH 测试

使用连接池和不使用连接池,它们之间的性能差距到底有多大呢?下面是一个简单的 JMH 测试例子,对 Redis 的 key 设置随机值。

@Fork(2)
@State(Scope.Benchmark)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)
@BenchmarkMode(Mode.Throughput)
public class JedisPoolVSJedisBenchmark {
   JedisPool pool = new JedisPool("localhost", 6379);

   @Benchmark
   public void testPool() {
       Jedis jedis = pool.getResource();
       jedis.set("a", UUID.randomUUID().toString());
       jedis.close();
   }

   @Benchmark
   public void testJedis() {
       Jedis jedis = new Jedis("localhost", 6379);
       jedis.set("a", UUID.randomUUID().toString());
       jedis.close();
   }
   //此处省略若干行
}

测试结果显示,使用连接池的方式吞吐量约为未使用连接池的 5 倍。

数据库连接池 HikariCP

HikariCP 源于日语“光る”,寓意软件工作速度如光速,它是 SpringBoot 中默认的数据库连接池。

它在池化技术之上通过编码技巧进一步提升性能,例如使用 FastList 替代 ArrayList、Javassist 优化字节码、实现无锁的 ConcurrentBag 等。

HikariCP 官方不推荐设置 minimumIdle,默认与 maximumPoolSize 相同;在实际业务中,数据库连接数 20~50 个通常足够,需根据业务属性调整。

对于不同业务类型,可考虑拆分连接池以避免资源争抢,或使用多个连接池分别处理快速响应和后台慢任务。

结果缓存池

池(Pool)与缓存(Cache)在保存对象或数据方面有相似之处,都是将加工后的结果存放在高速区域,以提升后续访问速度。

这种技术可以称为结果缓存池(Result Cache Pool),是一种综合性的优化手段。

小结

本文从 Commons Pool 2 开始,介绍了对象池的实现细节、关键参数及其调优,并通过 JMH 基准测试展示了 Jedis 使用池化后约 5 倍的性能提升;随后分析了 HikariCP 的高速特性和配置要点,建议在实际项目中合理设置池大小、超时和检测参数,以获得最佳性能。

当遇到对象创建成本高、创建耗时长且可重置复用的场景时,考虑使用对象池化技术,并结合监控和参数调优,实现系统性能的显著提升。

JavaPerformanceConnection PoolJedisHikariCPJMHCommons Pool
Architect's Tech Stack
Written by

Architect's Tech Stack

Java backend, microservices, distributed systems, containerized programming, and more.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.