版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第一文搞懂Java中对象池的实现目录1.什么是对象池2.为什么需要对象池3.对象池的实现4.开源的对象池工具5.JedisPool对象池实现分析6.对象池总结最近在分析一个应用中的某个接口的耗时情况时,发现一个看起来极其普通的对象创建操作,竟然每次需要消耗8ms左右时间,分析后发现这个对象可以通过对象池模式进行优化,优化后此步耗时仅有0.01ms,这篇文章介绍对象池相关知识。
1.什么是对象池
池化并不是什么新鲜的技术,它更像一种软件设计模式,主要功能是缓存一组已经初始化的对象,以供随时可以使用。对象池大多数场景下都是缓存着创建成本过高或者需要重复创建使用的对象,从池子中取对象的时间是可以预测的,但是新建一个对象的时间是不确定的。
当需要一个新对象时,就向池中借出一个,然后对象池标记当前对象正在使用,使用完毕后归还到对象池,以便再次借出。
常见的使用对象池化场景:
1.对象创建成本过高。2.需要频繁的创建大量重复对象,会产生很多内存碎片。3.同时使用的对象不会太多。4.常见的具体场景如数据库连接池、线程池等。
2.为什么需要对象池
如果一个对象的创建成本很高,比如建立数据库的连接时耗时过长,在不使用池化技术的情况下,我们的查询过程可能是这样的。
查询1:建立数据库连接-发起查询-收到响应-关闭连接查询2:建立数据库连接-发起查询-收到响应-关闭连接查询3:建立数据库连接-发起查询-收到响应-关闭连接
在这种模式下,每次查询都要重新建立关闭连接,因为建立连接是一个耗时的操作,所以这种模式会影响程序的总体性能。
那么使用池化思想是怎么样的呢?同样的过程会转变成下面的步骤。
初始化:建立N个数据库连接-缓存起来查询1:从缓存借到数据库连接-发起查询-收到响应-归还数据库连接对象到缓存查询2:从缓存借到数据库连接-发起查询-收到响应-归还数据库连接对象到缓存查询3:从缓存借到数据库连接-发起查询-收到响应-归还数据库连接对象到缓存
使用池化思想后,数据库连接并不会频繁的创建关闭,而是启动后就初始化了N个连接以供后续使用,使用完毕后归还对象,这样程序的总体性能得到提升。
3.对象池的实现
通过上面的例子也可以发现池化思想的几个关键步骤:初始化、借出、归还。上面没有展示销毁步骤,某些场景下还需要对象的销毁这一过程,比如释放连接。
下面我们手动实现一个简陋的对象池,加深下对对象池的理解。主要是定一个对象池管理类,然后在里面实现对象的初始化、借出、归还、销毁等操作。
package
com.wdbyet.tool.objectpool.mypool;
import
java.io.Closeable;
import
java.io.IOException;
import
java.util.HashSet;
import
java.util.Stack;
*
@author
public
class
MyObjectPoolT
extends
Closeable
{
//
池子大小
private
Integer
size
=
5;
//
对象池栈。后进先出
private
StackT
stackPool
=
new
Stack();
//
借出的对象的
hashCode
集合
private
HashSetInteger
borrowHashCodeSet
=
new
HashSet();
/**
*
增加一个对象
*
*
@param
t
*/
public
synchronized
void
addObj(T
t)
{
if
((stackPool.size()
+
borrowHashCodeSet.size())
==
size)
{
throw
new
RuntimeException("池中对象已经达到最大值");
}
stackPool.add(t);
System.out.println("添加了对象:"
+
t.hashCode());
}
/**
*
借出一个对象
*
*
@return
*/
public
synchronized
T
borrowObj()
{
if
(stackPool.isEmpty())
{
System.out.println("没有可以被借出的对象");
return
null;
}
T
pop
=
stackPool.pop();
borrowHashCodeSet.add(pop.hashCode());
System.out.println("借出了对象:"
+
pop.hashCode());
return
pop;
}
/**
*
归还一个对象
*
*
@param
t
*/
public
synchronized
void
returnObj(T
t)
{
if
(borrowHashCodeSet.contains(t.hashCode()))
{
stackPool.add(t);
borrowHashCodeSet.remove(t.hashCode());
System.out.println("归还了对象:"
+
t.hashCode());
return;
}
throw
new
RuntimeException("只能归还从池中借出的对象");
}
/**
*
销毁池中对象
*/
public
synchronized
void
destory()
{
if
(!borrowHashCodeSet.isEmpty())
{
throw
new
RuntimeException("尚有未归还的对象,不能关闭所有对象");
}
while
(!stackPool.isEmpty())
{
T
pop
=
stackPool.pop();
try
{
pop.close();
}
catch
(IOException
e)
{
throw
new
RuntimeException(e);
}
}
System.out.println("已经销毁了所有对象");
}
}
代码还是比较简单的,只是简单的示例,下面我们通过池化一个Redis连接对象Jedis来演示如何使用。
其实Jedis中已经有对应的Jedis池化管理对象了JedisPool了,不过我们这里为了演示对象池的实现,就不使用官方提供的JedisPool了。
启动一个Redis服务这里不做介绍,假设你已经有了一个Redis服务,下面引入Java中连接Redis需要用到的Maven依赖。
dependency
groupIdredis.clients/groupId
artifactIdjedis/artifactId
version4.2.0/version
/dependency
正常情况下Jedis对象的使用方式:
Jedis
jedis
=
new
Jedis("localhost",
6379);
String
name
=
jedis.get("name");
System.out.println(name);
jedis.close();
如果使用上面的对象池,就可以像下面这样使用。
package
com.wdbyet.tool.objectpool.mypool;
import
redis.clients.jedis.Jedis;
*
@author
niulang
*
@date
2025/07/02
public
class
MyObjectPoolTest
{
public
static
void
main(String[]
args)
{
MyObjectPoolJedis
objectPool
=
new
MyObjectPool();
//
增加一个
jedis
连接对象
objectPool.addObj(new
Jedis("",
6379));
objectPool.addObj(new
Jedis("",
6379));
//
从对象池中借出一个
jedis
对象
Jedis
jedis
=
objectPool.borrowObj();
//
一次
redis
查询
String
name
=
jedis.get("name");
System.out.println(String.format("redis
get:"
+
name));
//
归还
redis
连接对象
objectPool.returnObj(jedis);
//
销毁对象池中的所有对象
objectPool.destory();
//
再次借用对象
objectPool.borrowObj();
}
输出日志:
添加了对象:1556956098
添加了对象:1252585652
借出了对象:1252585652
redisget:
归还了对象:1252585652
已经销毁了所有对象
没有可以被借出的对象
如果使用JMH对使用对象池化进行Redis查询,和正常创建Redis连接然后查询关闭连接的方式进行性能对比,会发现两者的性能差异很大。下面是测试结果,可以发现使用对象池化后的性能是非池化方式的5倍左右。
BenchmarkModeCntScoreErrorUnits
MyObjectPoolTest.testthrpt152612.689358.767ops/s
MyObjectPoolTest.testPoolthrpt912414.22811669.484ops/s
4.开源的对象池工具
上面自己实现的对象池总归有些简陋了,其实开源工具中已经有了非常好用的对象池的实现,如Apache的commons-pool2工具,很多开源工具中的对象池都是基于此工具实现,下面介绍这个工具的使用方式。
maven依赖:
dependency
groupIdmons/groupId
artifactIdcommons-pool2/artifactId
version2.11.1/version
/dependency
在commons-pool2对象池工具中有几个关键的类。
PooledObjectFactory类是一个工厂接口,用于实现想要池化对象的创建、验证、销毁等操作。GenericObjectPool类是一个通用的对象池管理类,可以进行对象的借出、归还等操作。GenericObjectPoolConfig类是对象池的配置类,可以进行对象的最大、最小等容量信息进行配置。
下面通过一个具体的示例演示commons-pool2工具类的使用,这里依旧选择Redis连接对象Jedis作为演示。
实现PooledObjectFactory工厂类,实现其中的对象创建和销毁方法。
public
class
MyPooledObjectFactory
implements
PooledObjectFactoryJedis
{
@Override
public
void
activateObject(PooledObjectJedis
pooledObject)
throws
Exception
{
}
@Override
public
void
destroyObject(PooledObjectJedis
pooledObject)
throws
Exception
{
Jedis
jedis
=
pooledObject.getObject();
jedis.close();
System.out.println("释放连接");
}
@Override
public
PooledObjectJedis
makeObject()
throws
Exception
{
return
new
DefaultPooledObject(new
Jedis("localhost",
6379));
}
@Override
public
void
passivateObject(PooledObjectJedis
pooledObject)
throws
Exception
{
}
@Override
public
boolean
validateObject(PooledObjectJedis
pooledObject)
{
return
false;
}
}
继承GenericObjectPool类,实现对对象的借出、归还等操作。
public
class
MyGenericObjectPool
extends
GenericObjectPoolJedis
{
public
MyGenericObjectPool(PooledObjectFactory
factory)
{
super(factory);
}
public
MyGenericObjectPool(PooledObjectFactory
factory,
GenericObjectPoolConfig
config)
{
super(factory,
config);
}
public
MyGenericObjectPool(PooledObjectFactory
factory,
GenericObjectPoolConfig
config,
AbandonedConfig
abandonedConfig)
{
super(factory,
config,
abandonedConfig);
}
}
可以看到MyGenericObjectPool类的构造函数中的入参有GenericObjectPoolConfig对象,这是个对象池的配置对象,可以配置对象池的容量大小等信息,这里就不配置了,使用默认配置。
通过GenericObjectPoolConfig的源码可以看到默认配置中,对象池的容量是8个。
public
class
GenericObjectPoolConfigT
extends
BaseObjectPoolConfigT
{
/**
*
The
default
value
for
the
{@code
maxTotal}
configuration
attribute.
*
@see
GenericObjectPool#getMaxTotal()
*/
public
static
final
int
DEFAULT_MAX_TOTAL
=
8;
/**
*
The
default
value
for
the
{@code
maxIdle}
configuration
attribute.
*
@see
GenericObjectPool#getMaxIdle()
*/
public
static
final
int
DEFAULT_MAX_IDLE
=
8;
下面编写一个对象池使用测试类。
public
class
ApachePool
{
public
static
void
main(String[]
args)
throws
Exception
{
MyGenericObjectPool
objectMyObjectPool
=
new
MyGenericObjectPool(new
MyPooledObjectFactory());
Jedis
jedis
=
objectMyObjectPool.borrowObject();
String
name
=
jedis.get("name");
System.out.println(name);
objectMyObjectPool.returnObject(jedis);
objectMyObjectPool.close();
}
}
输出日志:
redisget:
释放连接
上面已经演示了commons-pool2工具中的对象池的使用方式,从上面的例子中可以发现这种对象池中只能存放同一种初始化条件的对象,如果这里的Redis我们需要存储一个本地连接和一个远程连接的两种Jedis对象,就不能满足了。那么怎么办呢?
其实commons-pool2工具已经考虑到了这种情况,通过增加一个key值可以在同一个对象池管理中进行区分,代码和上面类似,直接贴出完整的代码实现。
package
com.wdbyet.tool.objectpool.apachekeyedpool;
import
mons.pool2.BaseKeyedPooledObjectFactory;
import
mons.pool2.KeyedPooledObjectFactory;
import
mons.pool2.PooledObject;
import
mons.pool2.impl.AbandonedConfig;
import
mons.pool2.impl.DefaultPooledObject;
import
mons.pool2.impl.GenericKeyedObjectPool;
import
mons.pool2.impl.GenericKeyedObjectPoolConfig;
import
redis.clients.jedis.Jedis;
*
@author
*
@date
2025/07/07
public
class
ApacheKeyedPool
{
public
static
void
main(String[]
args)
throws
Exception
{
String
key
=
"local";
MyGenericKeyedObjectPool
objectMyObjectPool
=
new
MyGenericKeyedObjectPool(new
MyKeyedPooledObjectFactory());
Jedis
jedis
=
objectMyObjectPool.borrowObject(key);
String
name
=
jedis.get("name");
System.out.println("redis
get
:"
+
name);
objectMyObjectPool.returnObject(key,
jedis);
}
class
MyKeyedPooledObjectFactory
extends
BaseKeyedPooledObjectFactoryString,
Jedis
{
@Override
public
Jedis
create(String
key)
throws
Exception
{
if
("local".equals(key))
{
return
new
Jedis("localhost",
6379);
}
if
("remote".equals(key))
{
return
new
Jedis("05",
6379);
}
return
null;
}
@Override
public
PooledObjectJedis
wrap(Jedis
value)
{
return
new
DefaultPooledObject(value);
}
class
MyGenericKeyedObjectPool
extends
GenericKeyedObjectPoolString,
Jedis
{
public
MyGenericKeyedObjectPool(KeyedPooledObjectFactoryString,
Jedis
factory)
{
super(factory);
}
public
MyGenericKeyedObjectPool(KeyedPooledObjectFactoryString,
Jedis
factory,
GenericKeyedObjectPoolConfigJedis
config)
{
super(factory,
config);
}
public
MyGenericKeyedObjectPool(KeyedPooledObjectFactoryString,
Jedis
factory,
GenericKeyedObjectPoolConfigJedis
config,
AbandonedConfig
abandonedConfig)
{
super(factory,
config,
abandonedConfig);
}
}
输出日志:
redisget:
5.JedisPool对象池实现分析
这篇文章中的演示都使用了Jedis连接对象,其实在JedisSDK中已经实现了相应的对象池,也就是我们常用的JedisPool类。那么这里的JedisPool是怎么实现的呢?我们先看一下JedisPool的使用方式。
package
com.wdbyet.tool.objectpool;
import
redis.clients.jedis.Jedis;
import
redis.clients.jedis.JedisPool;
*
@author
public
class
JedisPoolTest
{
public
static
void
main(String[]
args)
{
JedisPool
jedisPool
=
new
JedisPool("localhost",
6379);
//
从对象池中借一个对象
Jedis
jedis
=
jedisPool.getResource();
String
name
=
jedis.get("name");
System.out.println("redis
get
:"
+
name);
jedis.close();
//
彻底退出前,关闭
Redis
连接池
jedisPool.close();
}
}
代码中添加了注释,可以看到通过jedisPool.getResource()拿到了一个对象,这里和上面commons-pool2工具中的borrowObject十分相似,继续追踪它的代码实现可以看到下面的代码。
//
redis.clients.jedis.JedisPool
//
public
class
JedisPool
extends
PoolJedis
{
public
Jedis
getResource()
{
Jedis
jedis
=
(Jedis)super.getResource();
jedis.setDataSource(this);
return
jedis;
//
继续追踪
super.getResource()
//
redis.clients.jedis.util.Pool
public
T
getResource()
{
try
{
return
super.borrowObject();
}
catch
(JedisException
var2)
{
throw
var2;
}
catch
(Exception
var3)
{
throw
new
JedisException("Could
not
get
a
resource
from
the
pool",
var3);
}
}
竟然看到了super.borrowObject(),多么熟悉的方法,继续分析代码可以发现Jedis对象池也是
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2026年南充科技职业学院单招职业技能测试题库及答案详解一套
- 2026年包头铁道职业技术学院单招职业技能考试题库带答案详解(a卷)
- 花艺环境设计师安全演练测试考核试卷含答案
- 感光材料乳剂熔化工班组管理考核试卷含答案
- 出河机司机复试能力考核试卷含答案
- 合成树脂生产工安全防护强化考核试卷含答案
- 生猪屠宰加工工7S考核试卷含答案
- 珂罗版制版员岗前冲突解决考核试卷含答案
- 妇女职业规划方案
- 乙烯-乙烯醇树脂装置操作工安全宣教水平考核试卷含答案
- 2026辽宁大连长兴岛经济技术开发区国有企业招聘渔港港站管理人员24人笔试备考试题及答案解析
- 2025年山东圣翰财贸职业学院单招职业技能考试模拟测试卷带答案解析
- GB 6441-2025生产安全事故分类与编码
- 湖北2025年湖北省京剧院招聘笔试历年参考题库附带答案详解
- 农业物资:2024年化肥农药销售合同模板
- 2024年03月深圳市深汕特别合作区机关事业单位2024年公开招考46名事务员笔试历年典型考题及考点研判与答案解析
- 2024北京背户车协议书范本
- 巨量引擎推广引流方案
- 中国美食菜谱大全
- 盘扣架安全施工方案审核要点
- 法律、法规识别与管理制度
评论
0/150
提交评论