性能优化-对象池
5.2 对象池
在上文提到的重量级对象特征是构造复杂,在使用此对象过程中,对象会缓存中间处理过程,如Jackson的ObjectMapper,Beetl的GroupTemplate,还有Gson序列化工具等等,还有一类重量级对象则是网络相关的客户端对象,如数据库连接池,以及Redis客户端Jedis使用JedisPool,这类是重量级别对象原因是建立网络链接是非常耗时的操作,应用希望一旦建立后,就会保持此链接。这时候,就需要使用池化技术。池化技术提供了对象重用机制
使用池化技术另外一个原因是减少对象的创建个数,总是复用之前创建的对象,从而减小垃圾回收时间
现在有一个对象链接Redis,定义如下
public class RedisClient {
String ip;
int port;
public RedisClient(String ip,int port){
this.ip = ip;
this.port = port;
}
public void connect(){
System.out.println("connect redis ");
}
public void close(){
System.out.println("close ");
}
}connect方法将会链接Redis 服务器,close方法则会管理链接,为了简单期间,省略了socket链接代码,RedisClient是一个重量级对象,可以使用使用Apache Commons Pool来实现对象池化,Commons Pool提供了BasePooledObjectFactory 可以方便的创建和管理对象,如下定义
public class RedisClientPooledObjectFactory extends BasePooledObjectFactory<RedisClient> {
String ip;
int port;
public RedisClientPooledObjectFactory(String ip,int port){
this.ip = ip;
this.port = port;
}
@Override
public RedisClient create() throws Exception {
RedisClient redisClient = new RedisClient(ip,port);
redisClient.connect();
return redisClient;
}
@Override
public PooledObject<RedisClient> wrap(RedisClient redisClient) {
return new DefaultPooledObject<RedisClient>(redisClient);
}
@Override
public void destroyObject(PooledObject<RedisClient> p) throws Exception{
RedisClient redisClient = p.getObject();
redisClient.close();
}
}通常需要实现create方法,用于创建重量级对象,wrap方法用于对象池管理,对象池不直接管理我们创建的对象,而是管理PooledObject,可以用DefaultPooledObject构造一个默认实现。destroyObject方法用于对象池销毁的时候,关闭重量级对象
有了RedisClientPooledObjectFactory,就可以构造GenericObjectPool,
public class RedisClientPoolUtil {
GenericObjectPool<RedisClient> genericObjectPool;
public void init(String ip,int port,int max){
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
config.setMaxTotal(max);
genericObjectPool = new GenericObjectPool(new RedisClientPooledObjectFactory(ip,port),config);
}
public RedisClient getOne() throws Exception {
return genericObjectPool.borrowObject();
}
public void returnObject(RedisClient redisClient){
genericObjectPool.returnObject(redisClient);
}
}如上代码构造了最多构造5个RedisClient的对象池,并封装了getOne方法用于从池里借出一个未使用的RedisClient,returnObject,把RedisClient归还到池里,如下代码测试
public class PoolTest {
static RedisClientPoolUtil util = new RedisClientPoolUtil();
static{
util.init("127.0.0.1",9090,3);
}
public static void main(String[] args) throws Exception {
RedisClient redisClient1 = util.getOne();
RedisClient redisClient2 = util.getOne();
RedisClient redisClient3 = util.getOne();
//会阻塞住,因为总共创建了3个
RedisClient redisClient4 = util.getOne();
}
}除了使用Common Pool技术外,还有更为简单的池化技术。比如用队列放入已经初始化好的对象。需要的时候从队列里取出,使用完毕放回队列。另外一个更为简单的方式是使用ThreadLocal来存放初始化好的对象。假定需要池化对象是要给字符数组,使用队列方式
/*定义一个需要重用得对象*/
public class Buffer{
char[] cs;
public Buffer(int size){
cs = new char[size];
}
public char[] getContent(){
return cs;
}
}使用队列方式
public class BufferPool{
prvate static BufferPool pool = new BufferPool();
ArrayBlockingQueue<Buffer> queue ;
private BufferPool(){
init();
}
/*初始化buffer*/
private void init(){
queue = new ArrayBlockingQueue<>(64);
for(int i=0;i<64;i++){
queue.put(new Buffer(1024);
}
}
/*采用单例模式*/
public static BufferPool instance(){
return this
}
public Buffer getBuffer(){
return queue.take()
}
public void release(Buffer buffer){
queue.put(buffer);
}
/*清空池*/
public void close(){
queue.clear()
}
}使用ThreadLocal方式如下
public class BufferPool{
static ThreadLocal<Buffer cache = ThreadLocal.withInitial((Supplier) () - new Buffer(1024));
public static Buffer getBuffer(){return cache.take()}}使用队列方式得优点是可以提供扩容”池“功能,缩容池,甚至释放池的功能。缺点是使用完毕必须归还,因此调用代码应该如下方式
public class BufferPool{
static ThreadLocal<Buffer> cache = ThreadLocal.withInitial((Supplier) () -> new Buffer(1024));
public static Buffer getBuffer(){
return cache.take()
}
}使用ThreadLocal的缺点是池的容量大小依赖于线程数,池的的容量是否可控,取决于调用时候的线程数,其创建和销毁依赖于线程的生命周期
ThreadLocal 不具备动态调整池大小功能,依赖线程数大小,如果担心内存溢出,可以使用SoftReference
public class BufferPool{
static ThreadLocal<SoftReference<Buffer>> cache = ThreadLocal.withInitial((Supplier) () ->
new SoftReference (new Buffer(1024)));
public static Buffer getBuffer(){
SoftReference<Buffer> srf = cache.take();
Buffer buffer = srf.get();
//判断是否被垃圾回收
if(buffer!=null}{
return buffer;
}
//如果回收了,重新构造一个
Buffer buffer = new Buffer(1024) ;
srf.set(buffer);
return buffer;
}
}