一个对象池包含一组已经初始化过且可以使用的对象,而可以在有需求时创建和销毁对象。池的用户可以从池子中取得对象,对其进行操作处理,并在不需要时归还给池子而非直接销毁它。这是一种特殊的工厂对象。
优点
复用池中对象,消除创建对象、回收对象 所产生的内存开销、cpu开销以及(若跨网络)产生的网络开销。
常见的使用对象池有:在使用socket时(包括各种连接池)、线程、数据库连接池等。
缺点
- 现在Java的对象分配操作不比c语言的malloc调用慢, 对于轻中量级的对象, 分配/释放对象的开销可以忽略不计;
- 并发环境中, 多个线程可能(同时)需要获取池中对象, 进而需要在堆数据结构上进行同步或者因为锁竞争而产生阻塞, 这种开销要比创建销毁对象的开销高数百倍;
- 由于池中对象的数量有限, 势必成为一个可伸缩性瓶颈;
- 很难正确的设定对象池的大小, 如果太小则起不到作用, 如果过大, 则占用内存资源高。
场景
- 资源受限的, 不需要可伸缩性的环境(cpu\内存等物理资源有限): cpu性能不够强劲, 内存比较紧张, 垃圾收集, 内存抖动会造成比较大的影响, 需要提高内存管理效率, 响应性比吞吐量更为重要;
- 数量受限的, 比如数据库连接;
- 创建成本高的对象, 可以考虑是否池化, 比较常见的有线程池(ThreadPoolExecutor), 字节数组池等。
使用
Apache 提供了一个通用的对象池技术的实现: Common Pool2,可以很方便的实现自己需要的对象池,而不需要自己实现一个对象池。
核心接口
-
ObjectPool
:对象池,持有对象并提供取/还等方法; -
PooledObjectFactory
:对象工厂,提供对象的创建、初始化、销毁等操作,由 Pool 调用。一般需要使用者自己实现这些操作; -
PooledObject
:池化对象,对池中对象的封装,封装对象的状态和一些其他信息。
Common Pool2 提供的最基本的实现就是由 Factory 创建对象并使用PooledObject
封装对象放入 Pool 中。
对象池实现
对象池有两个基础的接口 ObjectPool
和 KeyedObjectPool
, 持有的对象都是由 PooledObject
封装的池化对象。 KeyedObjectPool
的区别在于其是用键值对的方式维护对象。
ObjectPool
和 KeyedObjectPool
分别有一个默认的实现类GenericObjectPool
和 GenericKeyedObjectPool
可以直接使用,他们的公共部分和配置被抽取到了 BaseGenericObjectPool
中。
SoftReferenceObjectPool
是一个比较特殊的实现,在这个对象池实现中,每个对象都会被包装到一个SoftReference中。SoftReference允许垃圾回收机制在需要释放内存时回收对象池中的对象,可以避免一些内存泄露的问题。
ObjectPool
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();
...
}
PooledObjectFactory
对象工厂,负责对象的创建、初始化、销毁和验证等工作。Factory 对象由ObjectPool
持有并使用
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;
}
Common Pool2 并没有提供 PooledObjectFactory
可以直接使用的子类实现,因为对象的创建、初始化、销毁和验证的工作无法通用化,需要由使用方自己实现。不过它提供了一个抽象子类 BasePooledObjectFactory
,实现自己的工厂时可以继承BasePooledObjectFactory
,就只需要实现 create
和 wrap
两个方法了。
PooledObject
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();
}
对象池配置
对象池配置提供了对象池初始化所需要的参数,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 -
testWhileIdle
池中的闲置对象是否由逐出器验证。无法验证的对象将从池中删除销毁。默认 false -
timeBetweenEvictionRunsMillis
回收资源线程的执行周期,默认 -1 表示不启用回收资源线程 -
blockWhenExhausted
资源耗尽时,是否阻塞等待获取资源,默认 true
池化对象的状态
池化对象的状态定义在 PooledObjectState 枚举中,有以下值:
-
IDLE
在池中,处于空闲状态 -
ALLOCATED
被使用中 -
EVICTION
正在被逐出器验证 -
VALIDATION
正在验证 -
INVALID
驱逐测试或验证失败并将被销毁 -
ABANDONED
对象被客户端拿出后,长时间未返回池中,或没有调用 use 方法,即被标记为抛弃的
例子
// 资源类
public class Resource {
private static int id;
private int rid;
public Resource() {
synchronized (this) {
this.rid = id++;
}
}
public int getRid() {
return this.rid;
}
@Override
public String toString() {
return "id:" + this.rid;
}
}
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
// 工厂类
public class ResourcePoolableObjectFactory extends BasePooledObjectFactory<Resource>{
/**
* 创建一个对象实例
*/
@Override
public Resource create() throws Exception {
return new Resource();
}
/**
* 包裹创建的对象实例,返回一个pooledobject
*/
@Override
public PooledObject<Resource> wrap(Resource obj) {
return new DefaultPooledObject<Resource>(obj);
}
}
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
public class Test {
public static void main(String[] args) {
// 创建池对象工厂
PooledObjectFactory<Resource> factory = new ResourcePoolableObjectFactory();
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
// 最大空闲数
poolConfig.setMaxIdle(5);
// 最小空闲数, 池中只有一个空闲对象的时候,池会在创建一个对象,并借出一个对象,从而保证池中最小空闲数为1
poolConfig.setMinIdle(1);
// 最大池对象总数
poolConfig.setMaxTotal(20);
// 逐出连接的最小空闲时间 默认1800000毫秒(30分钟)
poolConfig.setMinEvictableIdleTimeMillis(1800000);
// 逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
poolConfig.setTimeBetweenEvictionRunsMillis(1800000 * 2L);
// 在获取对象的时候检查有效性, 默认false
poolConfig.setTestOnBorrow(true);
// 在归还对象的时候检查有效性, 默认false
poolConfig.setTestOnReturn(false);
// 在空闲时检查有效性, 默认false
poolConfig.setTestWhileIdle(false);
// 最大等待时间, 默认的值为-1,表示无限等待。
poolConfig.setMaxWaitMillis(5000);
// 是否启用后进先出, 默认true
poolConfig.setLifo(true);
// 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true
poolConfig.setBlockWhenExhausted(true);
// 每次逐出检查时 逐出的最大数目 默认3
poolConfig.setNumTestsPerEvictionRun(3);
// 创建对象池
final GenericObjectPool<Resource> pool = new GenericObjectPool<Resource>(factory, poolConfig);
for (int i = 0; i < 40; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Resource resource = pool.borrowObject();// 注意,如果对象池没有空余的对象,那么这里会block,可以设置block的超时时间
System.out.println(resource);
Thread.sleep(1000);
pool.returnObject(resource);// 申请的资源用完了记得归还,不然其他人要申请时可能就没有资源用了
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
}