Performance Testing of Apache Commons Pool2 GenericKeyedObjectPool in Java
This article demonstrates how to implement and benchmark Apache Commons Pool2's GenericKeyedObjectPool in Java, including factory creation, pool configuration, multithreaded performance tests with and without wait times, and analysis of results highlighting scalability limitations.
In the previous post I evaluated the performance of the generic object pool (GenericObjectPool) from Apache Commons Pool2; this article extends the discussion to the more complex GenericKeyedObjectPool, providing a complete example.
Hardware and software setup is the same as before, so it is omitted.
Pooling Factory
The factory extends org.apache.commons.pool2.BaseKeyedPooledObjectFactory<Integer, FunTesterPooled> . The full source code is shown below:
/**
* 池化工厂
*/
private static class FunFactory extends BaseKeyedPooledObjectFactory
{
@Override
FunTesterPooled create(Integer key) throws Exception {
def pooled = new FunTesterPooled()
pooled.setAge(key)
return pooled
}
@Override
PooledObject
wrap(FunTesterPooled value) {
return new DefaultPooledObject
(value)
}
@Override
void destroyObject(Integer key, PooledObject
p, DestroyMode destroyMode) throws Exception {
p.getObject().setAge(0)//资源回收
super.destroyObject(key, p, destroyMode)
}
}Object Pool Initialization
The pool is configured with parameters such as max total, min/max idle per key, and max wait time. The initialization method returns a GenericKeyedObjectPool<Integer, FunTesterPooled> instance.
static def initPool() {
def config = new GenericKeyedObjectPoolConfig
()
config.setMaxTotal(thread * 2)
config.setMinIdlePerKey(10)
config.setMaxIdlePerKey(100)
config.setMaxWait(Duration.ofMillis(1000))
config.setMaxIdlePerKey(thread)
config.setMaxIdlePerKey(10)
config.setMinIdlePerKey(2)
return new GenericKeyedObjectPool
(new FunFactory(), config)
}Performance Test Cases
The test adds a key parameter to the request and returns objects with the same key. The main method creates the pool, spawns multiple threads, borrows and returns objects, and finally closes the pool.
static GenericKeyedObjectPool
pool
static def desc = "池化框架性能测试"
static int times = 200
static int thread = 3
public static void main(String[] args) {
this.pool = initPool()
ThreadBase.COUNT = true
RUNUP_TIME = 0
POOL_SIZE = thread
thread.times {
pool.addObjects(it, thread)
}
output("对象创建完毕 创建数量${pool.getNumIdle()}")
new Concurrent(new FunTester(), thread, desc).start()
pool.close()
}
private static class FunTester extends FixedThread {
FunTester() {
super(null, times, true)
}
@Override
protected void doing() throws Exception {
def randomInt = getRandomInt(thread)
def object = pool.borrowObject(randomInt)
pool.returnObject(randomInt, object)
}
@Override
FunTester clone() {
return new FunTester()
}
}Test Results
Two scenarios were measured: without waiting and with a 2 ms sleep per operation. The tables below show thread count, execution count, and QPS.
线程数
执行次数(万)
QPS
1
300
189501
2
300
322603
5
300
120334
10
100
96861
20
50
81440
With waiting (2 ms sleep):
线程数
执行次数(k)
单线程QPS
20
10
416
50
10
393
100
5
222
200
2
79
300
2
27
The results indicate that QPS drops sharply as thread count increases, and performance becomes comparable to GenericObjectPool only at low thread counts. The degradation is likely caused by contention on java.util.concurrent.ConcurrentHashMap and other internal structures such as org.apache.commons.pool2.impl.LinkedBlockingDeque and java.util.concurrent.atomic.AtomicLong . Consequently, using an object pool as a simple replacement for object creation is not advisable at high concurrency.
Enjoy testing!
FunTester
10k followers, 1k articles | completely useless
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.