一、对象池是什么?
对象池模式经常用在频繁创建、销毁对象(并且对象创建、销毁开销很大)的场景,比如数据库连接池、线程池、任务队列池等。
使用对象池调用对象时,不使用常规的new 构造子的方式,而是通过一个对象池操作。即如果池中存在该对象,则取出;如果不存在,则新建一个对象并存储在池中。当使用完该对象后,则将该对象的归还给对象池。
二、使用步骤
1.引入库
代码如下(示例):
gradle:
compile group: 'org.apache.commons', name: 'commons-pool2', version: 2.10.0
maven:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.0</version>
</dependency>
2.对象实体
以下例子的对象为我实际某个项目中的实体,属性内容的意义可忽略
package com.iscas.sp.proxy.model;
import com.iscas.sp.filter.SpChain;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponse;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
/**
*
* @author zhuquanwen
* @vesion 1.0
* @date 2021/7/11 19:20
* @since jdk1.8
*/
@Data
@Accessors(chain = true)
public class SpContext {
private SpChain spChain = new SpChain();
/**当前所处的状态:pre 代理前,post 代理后*/
private String type = "pre";
/**netty的ctx*/
private ChannelHandlerContext ctx;
/**请求uri*/
private String uri;
/**请求参数*/
private Map<String, List<String>> params;
/**path*/
private String path;
/**是否需要keepalive*/
private boolean keepAlive;
/**请求*/
private FullHttpRequest request;
/**响应*/
private HttpResponse response;
// /**请求头*/
// private Map<String, String> requestHeaders = new HashMap<>();
// /**响应头*/
// private Map<String, String> responseHeaders = new HashMap<>();
/**请求头*/
private HttpHeaders reqHeaders;
/**响应头*/
private HttpHeaders resHeaders;
/**set-cookie*/
private List<Cookie> setCookies;
/**服务路由信息*/
private ServerInfo serverInfo;
/**当前代理的协议包括http/s、file*/
private String proxyType = "http/s";
/**返回值的流*/
private InputStream resIs;
}
3.生产对象的工厂
package com.iscas.sp.proxy.model.pool;
import com.iscas.common.tools.core.reflect.ReflectUtils;
import com.iscas.sp.filter.FilterFactory;
import com.iscas.sp.filter.IFilter;
import com.iscas.sp.filter.SpChain;
import com.iscas.sp.proxy.base.Constant;
import com.iscas.sp.proxy.model.Cookie;
import com.iscas.sp.proxy.model.ServerInfo;
import com.iscas.sp.proxy.model.SpContext;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponse;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
/**
*
* @author zhuquanwen
* @vesion 1.0
* @date 2021/8/4 15:26
* @since jdk1.8
*/
public class SpContextFactory extends BasePooledObjectFactory<SpContext> {
private static volatile GenericObjectPool<SpContext> pool = null;
public static GenericObjectPool<SpContext> getInstance() {
if (pool == null) {
synchronized (SpContextFactory.class) {
if (pool == null) {
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(-1);
poolConfig.setMaxIdle(Constant.PROXY_CONF.getMaximumPoolSize());
poolConfig.setMinIdle(Constant.PROXY_CONF.getCorePoolSize());
poolConfig.setLifo(false);
pool = new GenericObjectPool<>(new SpContextFactory(), poolConfig);
}
}
}
return pool;
}
@Override
public SpContext create() throws Exception {
return new SpContext();
}
@Override
public PooledObject<SpContext> wrap(SpContext obj) {
return new DefaultPooledObject<>(obj);
}
public static void clear() {
getInstance().clear();
}
public static void close() {
getInstance().close();
}
public static SpContext borrowObject() throws Exception {
return getInstance().borrowObject();
}
public static void returnObject(SpContext context) throws Exception {
getInstance().returnObject(context);
}
@Override
public void passivateObject(PooledObject<SpContext> p) throws Exception {
SpContext spContext = p.getObject();
//将对象的数据都设置为null
ReflectUtils.initObj(spContext, "spChain");
//spChain单独处理,设置其值为初始化值
SpChain spChain = spContext.getSpChain();
Field[] declaredFields = spChain.getClass().getDeclaredFields();
for (Field declaredField : declaredFields) {
declaredField.setAccessible(true);
if (Objects.equals("index", declaredField.getName())) {
AtomicInteger index = (AtomicInteger) declaredField.get(spChain);
index.set(0);
} else if (Objects.equals("preFinished", declaredField.getName())) {
declaredField.set(spChain, false);
} else if (Objects.equals("postFinished", declaredField.getName())) {
declaredField.set(spChain, false);
}
// else if (Objects.equals("filters", declaredField.getName())) {
// declaredField.set(spChain, FilterFactory.getFilters(Constant.PROXY_CONF));
// }
}
spContext.setType("pre");
spContext.setProxyType("http/s");
}
}
注意这里重写passivateObject的目的是在对象归还时为它初始化一些值。
4.使用对象池
以下代码是示例,重要的两行:
1.从对象池获取对象:spContext = SpContextFactory.borrowObject();
2.用完归还对象:SpContextFactory.returnObject(spContext);
SpContext spContext = null;
try {
spContext = SpContextFactory.borrowObject();
//封装context
spContext.setCtx(ctx);
wrapperContext(spContext, (FullHttpRequest) msg);
Constant.spContextThreadLocal.set(spContext);
//执行过滤器
SpChain spChain = spContext.getSpChain();
spChain.doFilter(spContext);
//如果preFinished为false,说明过滤器在中间停止了,不继续进行代理
if (spChain.isPreFinished()) {
httpHandler.handleHttpRequest();
}
} catch (Throwable e) {
//异常统一处理
HttpUtils.httpExceptionHandler(e);
} finally {
//清除threadlocal
Constant.spContextThreadLocal.remove();
try {
SpContextFactory.returnObject(spContext);
} catch (Exception exception) {
exception.printStackTrace();
}
HttpUtils.release(ctx, msg);
}
5.补充第三步代码中用到的ReflectUtils#initObj
/**
* 初始化对象
*/
public static void initObj(Object obj, String... ignoreFields) throws IllegalAccessException {
if (obj == null) {
return;
}
if (obj instanceof Map || obj instanceof Collection ||
obj.getClass().isArray()) {
throw new RuntimeException("仅支持JavaBean的初始化");
}
Class<?> aClass = obj.getClass();
Field[] declaredFields = aClass.getDeclaredFields();
if (declaredFields != null) {
label: for (Field declaredField : declaredFields) {
if (ignoreFields != null) {
for (String ignoreField : ignoreFields) {
if (Objects.equals(declaredField.getName(), ignoreField)) {
continue label;
}
}
}
declaredField.setAccessible(true);
Object val = null;
Class<?> type = declaredField.getType();
if (type == int.class) {
val = 0;
} else if (type == long.class) {
val = 0L;
} else if (type == short.class) {
val = 0;
} else if (type == byte.class) {
val = 0;
} else if (type == float.class) {
val = 0f;
} else if (type == double.class) {
val = 0d;
} else if (type == char.class) {
val = ' ';
} else if (type == boolean.class) {
val = false;
} else {
val = null;
}
declaredField.set(obj, val);
}
}
}
总结
以上介绍了对象池的概念,通过common-pool2包实现了对象池,并测试使用。 使用对象池有很多好处,但也得应用场景,优缺点总结如下:对象池的优点
复用池中对象,消除创建对象、回收对象 所产生的内存开销、cpu开销以及(若跨网络)产生的网络开销.
常见的使用对象池有:在使用socket时(包括各种连接池)、线程等等
对象池的缺点:
现在Java的对象分配操作不比c语言的malloc调用慢, 对于轻中量级的对象, 分配/释放对象的开销可以忽略不计;
并发环境中, 多个线程可能(同时)需要获取池中对象, 进而需要在堆数据结构上进行同步或者因为锁竞争而产生阻塞, 这种开销要比创建销毁对象的开销高数百倍;
由于池中对象的数量有限, 势必成为一个可伸缩性瓶颈;
很难正确的设定对象池的大小, 如果太小则起不到作用, 如果过大, 则占用内存资源高,
对象池有其特定的适用场景:
受限的, 不需要可伸缩性的环境(cpu\内存等物理资源有限): cpu性能不够强劲, 内存比较紧张, 垃圾收集, 内存抖动会造成比较大的影响, 需要提高内存管理效率, 响应性比吞吐量更为重要;
数量受限的资源, 比如数据库连接;
创建成本高昂的对象, 可斟酌是否池化, 比较常见的有线程池(ThreadPoolExecutor), 字节数组池等;