Build your own ObjectPool in Java to boost app speed
http://www.javaworld.com/javaworld/jw-06-1998/jw-06-object-pool.html
用Java来构建你的对象池(ObjectPool)来加快应用程序速度
-在减少内存需求的同时增加你的应用程序的速度
摘要
对象池允许实例对象的共享。由于不同的处理过程不需要重新实例化一定数量的对象,所以节省了加载时间。而且由于对象返回给对象池,就减少了垃圾收集的运行。通过作者的对象池设计的例子,可以学到如何使用这种方法来提升性能和最小化内存使用。
By Thomas E.Davis
当我第一次在一个实时,多用户的应用系统中实现对象池(特别是数据库连接池),平均交易时间从250毫秒下降到30毫秒。用户确实注意到并评论了性能的强烈的提高。
对象池的思想与你的当地图书馆借书的操作类似。当你想要读一本书时,你清楚从图书馆界会比自己买一份来得便宜。类似的,程序从对象池中借来实例将会比自己创建一个来的便宜(与内存速度相关)。换句话说,图书馆的书相当于实例,而借阅者相当于程序。当程序需要一个对象,从对象池里面获取而不是自己创建一个新对象。程序不再需要时将返回对象给对象池。
然而,对象池和图书馆有些小区别是需要了解的。如果借阅者需要一本特别的书,但是所有书都已经借走了,他就必须等到有一本归还。我们不想一个程序要等待一个对象,所以对象池需要时会实例化一个新对象。这将会导致大量对象存于对象池,所以必须标记未用对象和定时地清理。
我的对象池设计可以处理存储,跟踪,和超时,但是实例化,验证,和析构特定的对象类型必须由子类来处理。
现在基本上无碍,让我们开始代码。以下是框架:
public abstract class ObjectPool
{
private long expirationTime;
private Hashtable locked, unlocked;
abstract Object create();
abstract boolean validate( Object o );
abstract void expire( Object o );
synchronized Object checkOut(){...}
synchronized void checkIn( Object o ){...}
}
对象池内部存储要处理两个Hashtable对象,一个是locked对象另一个是unlocked。对象自己作为hashtable的key而其最后使用时间(用微秒)作为value。存储一个对象最后使用的时间,对象池在其经过一段不活动时期后可以终止它和释放内存。
最后,对象池允许子类来指定hashtable的初始大小和增长速度和终止时间。但由于这篇文章的目的,我通过在构造函数中硬编码其值来尽量使它简单。
ObjectPool()
{
expirationTime = 30000; // 30 seconds
locked = new Hashtable();
unlocked = new Hashtable();
}
方法checkOut()首先检查unlocked hashtable是否有对象。如果有,遍历它们来寻找有效的对象。验证依赖于两件事。首先对象池检查对象的最后使用时间没有超过子类指定的终止时间。其次,对象池调用抽象方法validate(),其进行class细节检查和需要重使用的重新初始化。如果验证失败,将会释放它并遍历下个对象。如果有个对象通过了验证,把它移到locked hashtable并返回给申请程序。如果unlocked hashtable为空,或者所有对象未通过验证,将会新创建一个对象并返回。
synchronized Object checkOut()
{
long now = System.currentTimeMillis();
Object o;
if( unlocked.size() > 0 )
{
Enumeration e = unlocked.keys();
while( e.hasMoreElements() )
{
o = e.nextElement();
if( ( now - ( ( Long ) unlocked.get( o ) ).longValue() ) >
expirationTime )
{
// object has expired
unlocked.remove( o );
expire( o );
o = null;
}
else
{
if( validate( o ) )
{
unlocked.remove( o );
locked.put( o, new Long( now ) );
return( o );
}
else
{
// object failed validation
unlocked.remove( o );
expire( o );
o = null;
}
}
}
}
// no objects available, create a new one
o = create();
locked.put( o, new Long( now ) );
return( o );
}
这是对象池中最复杂的一个方法,其他都会简单。方法checkIn() 简单的把传进来的对象从locked hashtable移到unlocked hashtable。
synchronized void checkIn( Object o )
{
locked.remove( o );
unlocked.put( o, new Long( System.currentTimeMillis() ) );
}
三个剩下的方法是抽象方法,必须由子类实现。由于这篇文章的目的,我将创建一个数据库连接池JDBCConnectionPool。下面是框架:
public class JDBCConnectionPool extends ObjectPool
{
private String dsn, usr, pwd;
public JDBCConnectionPool(){...}
create(){...}
validate(){...}
expire(){...}
public Connection borrowConnection(){...}
public void returnConnection(){...}
}
JDBCConnectionPool需要应用程序在实例化时(构造函数)指定数据库驱动,DSN,用户名,密码。如果这让你迷惑,别担心,JDBC是另一个主题。暂时忍受下,直到对象池的结束。
public JDBCConnectionPool( String driver, String dsn, String usr, String pwd )
{
try
{
Class.forName( driver ).newInstance();
}
catch( Exception e )
{
e.printStackTrace();
}
this.dsn = dsn;
this.usr = usr;
this.pwd = pwd;
}
现在我们可以钻研那些抽象方法的实现了。如你所见,方法checkOut() 里面对象池需要一个新对象时会调用子类create() 。对于JDBCConnectionPool,我们需要做的就是新建一个连接对象并传回。再一次,为了保持本文简单,我抛弃风的警告,忽略所有异常和null pointer条件。
Object create()
{
try
{
return( DriverManager.getConnection( dsn, usr, pwd ) );
}
catch( SQLException e )
{
e.printStackTrace();
return( null );
}
}
在对象池为了垃圾收集释放和终止一个对象之前,需要时传递它给子类方法expire()来进行最后的清理(很像垃圾收集调用的finalize() )。在JDBCConnectionPool这个例子里面,我们需要做的就是关闭连接。
void expire( Object o )
{
try
{
( ( Connection ) o ).close();
}
catch( SQLException e )
{
e.printStackTrace();
}
}
最后,我们必须实现方法validate(),对象池会调用其来保证对象是有效的。这也是需要时可以重新初始化的地方。对于JDBCConnectionPool,我们只是检查连接是否打开。
boolean validate( Object o )
{
try
{
return( ! ( ( Connection ) o ).isClosed() );
}
catch( SQLException e )
{
e.printStackTrace();
return( false );
}
}
这就是内在的功能。JDBCConnectionPool允许应用程序通过难于相信的简单和适当命名的方法使用数据库连接。
public Connection borrowConnection()
{
return( ( Connection ) super.checkOut() );
}
public void returnConnection( Connection c )
{
super.checkIn( c );
}
设计还有一些缺陷。也许最大的就是可能创建了一大堆从不释放的对象。举个例子,如果一串应用程序同时向对象池请求一个对象,对象池会创建所有需要的对象。然后,如果所有应用程序归还那些对象,但是checkOut() 将不再调用,所有的对象都得不到清理。这是在现行应用程序中罕有的事件,但“有空闲时间”的后端应用程序可能会产生这样的情况。我用一个清理线程来解决这个设计问题,但我把这个讨论放到下来的文章里面。我也会覆盖错误处理,异常传播等的处理来使对象池在关键任务(关键任务)之应用中更robust。
我欢迎任何的评论,批判,和或者你提出的设计改进。