大多时候,我们获取对象的方法都是直接 new 一个。但是,对于大对象的构造,或者构造耗时比较久的对象,我们每次要使用都去 new 一个是很不科学的。比如数据库的连接对象、Redis 的连接对象、Http 连接请求对象等等。在设计模式中有一个专门的模式来解决这种场景下的问题,即“享元模式”。
“享元模式”其实很好理解,也就是构造一个对象池,这个对象池中维护一定数量的对象,需要的时候就从这个对象池中获取对象,使用完后返还给对象池。这样就避免构造对象所带来的耗时,提升了系统的性能。
设计这样的一个对象池看起来好像并不难,甚至觉的只需要一个 List 就可以做到。但是,如果考虑到系统的伸缩性,比如在系统忙时可能需要对象池中有足够的对象可以被拿来使用,以保证系统大多时候应该等待对象而进入阻塞,同时,在系统闲时又不需要太多的对象存放在对象池中,这时候就需要释放一些对象。另外,还需要考虑对象何时构造,何时销毁,对象异常的处理等问题。
为了让大多 java 程序员不重复造轮子,apache 开发了一个库,专门给需要对象池的程序提供一个底层的支持。这个库也就是 apche commons-pool2,使用这个库,我们只需要关注对象的生成、销毁、校验等操作就可以了。对象池的具体实现细节都交给 commons-pool2 中的具体对象池实现类来完成。
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.8.0</version>
</dependency>
apache commons-pool2 包提供了一个通用的对象池技术的实现。可以很方便的基于它来实现自己的对象池,比如 DBCP 和 Jedis 他们的内部对象池的实现就是依赖于 commons-pool2 。
commons-pool2 由三大模块组成:ObjectPool 、PooledObject 和 PooledObjectFactory 。
ObjectPool :提供所有对象的存取管理。
PooledObject :池化的对象,是对真正对象的一个包装,加上了对象的一些其他信息,包括对象的状态(已用、空闲),对象的创建时间等。其实现要求是线程安全的。
PooledObjectFactory :工厂类,负责池化对象的创建,对象的初始化,对象状态的销毁和对象状态的验证。其实现要求是线程安全的。
通常,ObjectPool 会持有 PooledObjectFactory ,将具体的对象的创建、初始化、销毁等任务委托给 PooledObjectFactory 处理,工厂操作的对象是 PooledObject ,即具体的 Object 的包装类。因此,获取对象依赖 ObjectPool 而不是 PooledObjectFactory 。
ObjectPool 的主要方法:
//从对象池中获取对象的方法
T borrowObject() throws Exception, NoSuchElementException,
IllegalStateException;
//将对象返还给对象池
void returnObject(T obj) throws Exception;
//让对象失效
void invalidateObject(T obj) throws Exception;
//往对象池中新增一个对象
void addObject() throws Exception, IllegalStateException,
UnsupportedOperationException;
//获取当前闲置在对象池中的对象数量,即没有被拿走使用的对象数量
int getNumIdle();
//获取已经在使用中的对象数量,即被使用者从对象池中拿走使用的数量
int getNumActive();
//清空对象池中闲置的所有对象
void clear() throws Exception, UnsupportedOperationException;
//关闭对象池
void close();
PooledObject ,抽象了对象池中对象应该具备的一些属性。注意,这个对象并不是我们真正要存的对象,而是经过一层封装的对象,那么真正存放在对象池的其实都是经过封装过的对象,即 PooledObject 对象。
public interface PooledObject<T> extends Comparable<PooledObject<T>> {
T getObject();
long getCreateTime();
long getActiveTimeMillis();
long getIdleTimeMillis();
long getLastBorrowTime();
long getLastReturnTime();
long getLastUsedTime();
@Override
int compareTo(PooledObject<T> other);
@Override
boolean equals(Object obj);
@Override
int hashCode();
String toString();
boolean startEvictionTest();
boolean endEvictionTest(Deque<PooledObject<T>> idleQueue);
boolean allocate();
boolean deallocate();
void invalidate();
void setLogAbandoned(boolean logAbandoned);
void use();
void printStackTrace(PrintWriter writer);
PooledObjectState getState();
void markAbandoned();
void markReturning();
}
PooledObjectFactory 抽象工厂模型:
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;
}
创建对象工厂 PooledHttpClientFactory :
public class PooledHttpClientFactory implements PooledObjectFactory<HttpClient> {
@Override
public PooledObject<HttpClient> makeObject() throws Exception {
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
return new DefaultPooledObject<>(httpClient);
}
@Override
public void destroyObject(PooledObject<HttpClient> pooledObject) throws Exception {
//不处理
}
@Override
public boolean validateObject(PooledObject<HttpClient> pooledObject) {
return true;
}
@Override
public void activateObject(PooledObject<HttpClient> pooledObject) throws Exception {
//不处理
}
@Override
public void passivateObject(PooledObject<HttpClient> pooledObject) throws Exception {
//不处理
}
}
测试:
public static void main(String[] args) {
GenericObjectPoolConfig httpClientPoolConfig = new GenericObjectPoolConfig();
httpClientPoolConfig.setMaxTotal(100);
httpClientPoolConfig.setMaxIdle(10);
PooledHttpClientFactory httpClientFactory = new PooledHttpClientFactory();
GenericObjectPool<HttpClient> httpClientPool = new GenericObjectPool<>(httpClientFactory, httpClientPoolConfig);
try {
HttpClient httpClient = httpClientPool.borrowObject();
try {
RequestBuilder requestBuilder = RequestBuilder.get("https://www.oschina.net");
requestBuilder.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 Safari/537.36");
HttpUriRequest httpUriRequest = requestBuilder.build();
HttpClientContext httpClientContext = new HttpClientContext();
HttpResponse httpResponse = httpClient.execute(httpUriRequest, httpClientContext);
byte[] contentBytes = IOUtils.toByteArray(httpResponse.getEntity().getContent());
System.out.println("获取的网页源码:" + new String(contentBytes, "utf-8"));
httpClientPool.returnObject(httpClient);
} catch (Exception ex) {
httpClientPool.invalidateObject(httpClient);
ex.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
属性 | 说明 |
---|---|
maxTotal | 允许创建对象的最大数量,默认值 8 ,-1 代表无数量限制( int 类型) |
maxIdle | 最大空闲资源数,默认值 8 |
minIdle | 最小空闲资源数,默认值 0 ,软标准情况下,至少留下几个 object 继续保持 idle(节省开销),软标准,结合 idleSoftEvictTime 和 timeBetweenEvictionRunsMillis 使用 |
lifo | 对象池存储空闲对象是使用的 LinkedBlockingDeque ,它本质上是一个支持 FIFO 和 FILO 的双向的队列,commons-pool2 中的 LinkedBlockingDeque 不是 Java 原生的队列,而有 commons-pool2 重新写的一个双向队列。如果为 true ,表示使用 FIFO 获取对象(资源的存取数据结构,默认值 true ,true 资源按照栈结构存取,false 资源按照队列结构存取) |
fairness | 从池中获取/返还对象时是否使用公平锁机制,默认为 false |
maxWaitMillis | 获取对象时的等待时间,单位毫秒。当 blockWhenExhausted 配置为 true 时,此值有效。 -1 代表无时间限制,一直阻塞直到有可用的资源( long 类型) |
minEvictableIdleTimeMillis | 对象空闲的最小时间,达到此值后空闲对象将可能会被移除。-1 表示不移除;默认 30 分钟,在资源回收策略中,会使用到 |
softMinEvictableIdleTimeMillis | 同上,额外的条件是池中至少保留有 minIdle 所指定的个数的对象在资源回收策略中,会使用到 |
numTestsPerEvictionRun | 资源回收线程执行一次回收操作,回收资源的数量。默认 3 。当设置为 0 时,不回收资源。 设置为小于 0 时,回收资源的个数为 (int)Math.ceil(池中空闲资源个数/Math.abs(numTestsPerEvictionRun));设置为大于 0 时,回收资源的个数为 Math.min(numTestsPerEvictionRun,池中空闲的资源个数) |
testOnCreate | 创建对象时是否调用 factory.validateObject 方法,默认 false |
testOnBorrow | 取对象时是否调用 factory.validateObject 方法,默认 false |
testOnReturn | 返还对象时是否调用 factory.validateObject 方法,默认 false 。当将资源返还个资源池时候,验证资源的有效性,调用 factory.validateObject()方法,如果无效,则调用 factory.destroyObject() 方法 |
testWhileIdle | 池中的闲置对象是否由逐出器验证。无法验证的对象将从池中删除销毁。默认 false |
timeBetweenEvictionRunsMillis | 回收资源线程的执行周期,默认 -1 ,表示不启用回收资源线程,即不开启驱逐线程,所以与之相关的参数是没有作用的 |
minEvictableIdleTimeMillis | 最小的驱逐时间,对象最小的空闲时间。如果为小于等于 0 ,取 Long 的最大值,如果大于 0 ,当空闲的时间大于这个值时,执行移除这个对象操作。默认值是 1000L*60L*30L;即 30 分钟。可以避免(连接)泄漏 |
SoftMinEvictableIdleTimeMillis | 也是最小的驱逐时间,对象最小的空间时间,如果小于等于 0 ,取 Long 的最大值,如果大于 0 ,当对象的空闲时间超过这个值,并且当前空闲对象的数量大于最小空闲数量(minIdle)时,执行移除操作。这个和上面的 minEvictableIdleTimeMillis 的区别是,它会保留最小的空闲对象数量。而上面的不会,是强制性移除的。默认值是 -1 ,通常该值设置的比 minEvictableIdleTimeMillis 要小 |
evictionPolicyClassName | 资源回收策略,默认值 org.apache.commons.pool2.impl.DefaultEvictionPolicy 驱逐线程使用的策略类名,之前的 minEvictableIdleTimeMillis 和 softMinEvictableIdleTimeMillis 就是默认策略 DefaultEvictionPolicy 的实现,这个两个参数,在资源回收策略中,会使用到 |
blockWhenExhausted | 资源耗尽时,是否阻塞等待获取资源,默认 true |
whenExhaustedAction | 指定在池中借出对象的数目已达极限的情况下,调用它的 borrowObject 方法时的行为。可以选用的值有:GenericObjectPool.WHEN_EXHAUSTED_BLOCK ,表示等待;GenericObjectPool.WHEN_EXHAUSTED_GROW ,表示创建新的实例(不过这就使 maxActive 参数失去了意义);GenericObjectPool.WHEN_EXHAUSTED_FAIL ,表示抛出一 java.util.NoSuchElementException 异常 |