一文搞懂Java中对象池的实现_第1页
一文搞懂Java中对象池的实现_第2页
一文搞懂Java中对象池的实现_第3页
一文搞懂Java中对象池的实现_第4页
一文搞懂Java中对象池的实现_第5页
已阅读5页,还剩12页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第一文搞懂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. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论