对象池模式

一种特殊的工厂对象,一个对象池包含一组已经初始化过且可以使用的对象。用户可以从池中取得对象,并对其进行操作,在不需要的时候将其归还给对象池。

使用对象池的目的是为了提升性能,当需要创建比较耗费时间的对象时尤为明显。

对象池通用类图

角色和职责

对象池的管理者

管理者来维护对象池,包括初始化对象池、扩充对象池的大小、获取对象时的策略、重置归还对象的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class PoolMgr{

private int init ;

private int max;

private int factor;

private Colection c; //

public void init(){
//init
}
//根据策略来动态的生成对象。。
//如果对象不足可以增加对象池的对象,或者blocking直到有对象释放。
public Object aquire(){

return c.remove();
}

public void release(Object o){
if(o == null) return;

reset(o);
c.add(o);
}

}

对象池的使用者

使用者从管理者处获取对象,使用完成后必须归还对象避免一致占用。

1
2
3
4
5
6
Object o = null;
try{
o = PoolMgr.getInstance();
}finaly{
PoolMgr.release(o);
}

优点

复用池中的对象,消除创建/回收对象所产生的内存开销,CPU开销以及IO开销。
常见的有:Socket的链接池、线程池、数据库连接池等。

缺点

多线程并发环境中,需要考虑线程安全问题,需要在数据结构上进行同步或者为锁竞争产生阻塞,这种开销处理不好会比创建销毁独享的开销高很多。
由于池中的对象数量有限,对象池小可能会成为性能瓶颈,太大占用内存资源高;
轻量级对象的创建/销毁不需要使用对象池,徒增系统的复杂度;

Common Pool2

Commons Pool2核心部分由三个基础接口和相关的实现类实现:

PooledObject

池化对象,对池中的对象进行封装,封装对象的状态和一些其他信息。
PooldedObject 有两个实现类,DefaultPoolObject时普通通用的实现,PooledSoftReference使用SoftReference封装了对象,供SoftReferenceObjectPool使用。

池化对象类图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public interface PooledObject<T> extends Comparable<PooledObject<T>> {
// 获取封装的对象
T getObject();

// 对象创建的时间
long getCreateTime();

// 对象上次处于活动状态的时间
long getActiveTimeMillis();

// 对象上次处于空闲状态的时间
long getIdleTimeMillis();

// 对象上次被借出的时间
long getLastBorrowTime();

// 对象上次返还的时间
long getLastReturnTime();

// 对象上次使用的时间
long getLastUsedTime();

// 将状态置为 PooledObjectState.INVALID
void invalidate();

// 更新 lastUseTime
void use();

// 获取对象状态
PooledObjectState getState();

// 将状态置为 PooledObjectState.ABANDONED
void markAbandoned();

// 将状态置为 PooledObjectState.RETURNING
void markReturning();
}

PooledObjectFactory

池化对象工厂,负责对象的创建、初始化、销毁和验证工作,Factory对象由ObjectPool持有并使用。
因为对象的创建、初始化、销毁和仰正的工作无法通用,Commons Pool2并没有提供PooledObjectFactory的默认子类实现,只提供了抽象子类BasePooledObjectFactory
可以实现create和wrap两个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface PooledObjectFactory<T> {
// 创建一个池对象
PooledObject<T> makeObject() throws Exception;

// 销毁对象
void destroyObject(PooledObject<T> p) throws Exception;

// 验证对象是否可用
boolean validateObject(PooledObject<T> p);

// 激活对象,从池中取对象时会调用此方法
void activateObject(PooledObject<T> p) throws Exception;

// 钝化对象,向池中返还对象时会调用此方法
void passivateObject(PooledObject<T> p) throws Exception;
}

ObjectPool

持有对象并提供取/还等操作.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 public interface ObjectPool<T> {
// 从池中获取一个对象,客户端在使用完对象后必须使用 returnObject 方法返还获取的对象
T borrowObject() throws Exception, NoSuchElementException,
IllegalStateException;

// 将对象返还到池中。对象必须是从 borrowObject 方法获取到的
void returnObject(T obj) throws Exception;

// 使池中的对象失效,当获取到的对象被确定无效时(由于异常或其他问题),应该调用该方法
void invalidateObject(T obj) throws Exception;

// 池中当前闲置的对象数量
int getNumIdle();

// 当前从池中借出的对象的数量
int getNumActive();

// 清除池中闲置的对象
void clear() throws Exception, UnsupportedOperationException;

// 关闭这个池,并释放与之相关的资源
void close();

...
}

对象池配置

对象池配置提供了对象池初始化所需要的参数,Common Pool2中的基础配置类时BaseObjectPoolConfig, 有GenericObjectPoolConfig和GenericKeyedObjectPoolConfig,分别为GenericObjectPool和GenericKeyedObjectPool使用。

参数 解释 默认值
lifo 连接池存放池对象的方式; true:放在空闲队列的嘴钱买呢, false:放在空闲队列的最后面 true
fairness 从池中获取/返还对象时是否使用公平锁机制 false
maxWaitMillis 获取资源的等待时间。blockWhenExhausted为true时有效。-1代表无时间限制,一致阻塞到有可用的资源
minEvictableIdleTimeMillis 对象空闲的最小时间,打到此值后空闲对象可能会被移除,-1表示不移除 30分钟
softMinEvictableIdletimeMillis 对象空闲的最小时间,并且池中之少保留有minidle所指定的个数
numTestsPerEvictionRun 资源回收线程执行一次回收操作回收资源的个数 3
evictionPolicyClassName 资源回收策略 org.apache.commons.pool2.impl.DefaultEvictionPolicy
testOnCreate 创建对象时是否调用factory.validateObject false
testOnBorrow 获取对形势是否调用factory.validateObject false
testOnReturn 返还对象时是否调用factory.validateObject false
timeBetweenEvictionRunsMills 回收资源线程的执行周期,默认-1表示不启用回收资源线程 -1
blockWhenExhausted 资源耗尽时,是否阻塞等待获取资源 true
testWhileIdel 池中的限制对象是否由逐出器验证,无法验证的对象将从池中删除销毁 false

池化对象的状态

参数值 描述
IDEL 在池中,处于空闲状态
ALLOCATED 被使用中
EVICTION 正在被逐出器验证
VALIDAITTON 正在验证
INVALID 驱逐测试或者验证失败并将被销毁
ABANDONED 对象被客户端拿出后,长时间未返回池中,或者没有调用use方法,被标记为抛弃

Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class StringBufferFactory extends BasePooledObjectFactory<StringBuffer> {
// 创建一个新的对象
@Override
public StringBuffer create() {
return new StringBuffer();
}

// 封装为池化对象
@Override
public PooledObject<StringBuffer> wrap(StringBuffer buffer) {
return new DefaultPooledObject<>(buffer);
}

// 使用完返还对象时将 StringBuffer 清空
@Override
public void passivateObject(PooledObject<StringBuffer> pooledObject) {
pooledObject.getObject().setLength(0);
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
 // 创建对象池配置
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
// 创建对象工厂
PooledObjectFactory factory = new StringBufferFactory();
// 创建对象池
ObjectPool<StringBuffer> pool = new GenericObjectPool<>(factory, config);
StringReader in = new StringReader("abcdefg");
StringBuffer buf = null;
try {
// 从池中获取对象
buf = pool.borrowObject();

// 使用对象
for (int c = in.read(); c != -1; c = in.read()) {
buf.append((char) c);
}
return buf.toString();
} catch (Exception e) {
try {
// 出现错误将对象置为失效
pool.invalidateObject(buf);
// 避免 invalidate 之后再 return 抛异常
buf = null;
} catch (Exception ex) {
// ignored
}
throw e;
} finally {
try {
in.close();
} catch (Exception e) {
// ignored
}

try {
if (null != buf) {
// 使用完后必须 returnObject
pool.returnObject(buf);
}
} catch (Exception e) {
// ignored
}
}

参考:
Apache Common Pool2 对象池应用浅析
一个广为人知但鲜有人用的技巧:对象池