转载自:http://it.deepinmind.com/java/2014/02/28/concurrent-object-pooling-in-java.html
这篇文章主要讨论下如何在Java里实现一个对象池,关键是设计思路。
在开始写代码前,先来梳理下一个对象池需要完成哪些功能:如果有可用的对象,对象池应当能返回给客户端。 客户端把对象放回池里后,可以对这些对象进行重用。 对象池能够创建新的对象来满足客户端不断增长的需求。 需要有一个正确关闭池的机制来确保关闭后不会发生内存泄露。
声明的接口如下:
现在来实现一下这个接口。开始动手之前,需要注意,一个理想的release方法应该先尝试检查下这个客户端返回的对象是否还能重复使用。如果是的话再把它扔回池里,如果不是,就舍弃掉这个对象。我们希望这个Pool接口的所有实现都能遵循这个规则。在开始具体的实现类前,我们先创建一个抽象类,以便限制后续的实现能遵循这点。我们实现的抽象类就叫做AbstractPool,它的定义如下:
有了上面这几个类,我们就可以着手开始具体的实现了。不过还有个问题,由于上面这些类是设计成能支持通用的对象池的,因此具体的实现不知道该如何验证对象的有效性(因为对象都是泛型的)。因此我们还需要些别的东西来帮助我们完成这个。
我们需要一个通用的方法来完成对象的校验,而具体的实现不必关心对象是何种类型。因此我们引入了一个新的接口,Validator,它定义了验证对象的方法。这个接口的定义如下:
这里是BoundedBlockingPool的声明:
现在我们将在自己的代码中使用上面这个对象池,用它来缓存数据库连接。我们需要一个校验器来验证数据库连接是否有效。
下面是这个JDBCConnectionValidator:
这篇文章主要讨论下如何在Java里实现一个对象池,关键是设计思路。
在开始写代码前,先来梳理下一个对象池需要完成哪些功能:如果有可用的对象,对象池应当能返回给客户端。 客户端把对象放回池里后,可以对这些对象进行重用。 对象池能够创建新的对象来满足客户端不断增长的需求。 需要有一个正确关闭池的机制来确保关闭后不会发生内存泄露。
声明的接口如下:
public interface Pool<T> {
T get();
void release(T t);
void shutdown();
}
为了能够支持任意对象,上面这个接口故意设计得很简单通用。它提供了从池里获取/返回对象的方法,还有一个关闭池的机制,以便释放对象。
现在来实现一下这个接口。开始动手之前,需要注意,一个理想的release方法应该先尝试检查下这个客户端返回的对象是否还能重复使用。如果是的话再把它扔回池里,如果不是,就舍弃掉这个对象。我们希望这个Pool接口的所有实现都能遵循这个规则。在开始具体的实现类前,我们先创建一个抽象类,以便限制后续的实现能遵循这点。我们实现的抽象类就叫做AbstractPool,它的定义如下:
public abstract class AbstractPool<T> implements Pool<T> {
@Override
public final void release(T t) {
if (isValid(t)) {
returnToPool(t);
} else {
handleInvalidReturn(t);
}
}
protected abstract void handleInvalidReturn(T t);
protected abstract void returnToPool(T t);
protected abstract boolean isValid(T t);
}
在上面这个类里,我们让对象池必须得先验证对象后才能把它放回到池里。具体的实现可以自由选择如何实现这三种方法,以便定制自己的行为。它们根据自己的逻辑来决定如何判断一个对象有效,无效的话应该怎么处理(handleInvalidReturn方法),怎么把一个有效的对象放回到池里(returnToPool方法)。
有了上面这几个类,我们就可以着手开始具体的实现了。不过还有个问题,由于上面这些类是设计成能支持通用的对象池的,因此具体的实现不知道该如何验证对象的有效性(因为对象都是泛型的)。因此我们还需要些别的东西来帮助我们完成这个。
我们需要一个通用的方法来完成对象的校验,而具体的实现不必关心对象是何种类型。因此我们引入了一个新的接口,Validator,它定义了验证对象的方法。这个接口的定义如下:
public static interface Validator<T> {
public boolean isValid(T t);
public void invalidate(T t);
}
上面这个接口定义了一个检验对象的方法,以及一个把对象置为无效的方法。当准备废弃一个对象并清理内存的时候,invalidate方法就派上用场了。值得注意的是这个接口本身没有任何意义,只有当它在对象池里使用的时候才有意义,所以我们把这个接口定义到Pool接口里面。这和Java集合库里的Map和Map.Entry是一样的。所以我们的Pool接口就成了这样:
public interface Pool<T> {
T get();
void release(T t);
void shutdown();
public static interface Validator<T> {
public boolean isValid(T t);
public void invalidate(T t);
}
}
准备工作已经差不多了,在最后开始前我们还需要一个终极武器,这才是这个对象池的杀手锏。就是“能够创建新的对象”。我们的对象池是泛型的,因此它们得知道如何去生成新的对象来填充这个池子。这个功能不能依赖于对象池本身,必须要有一个通用的方式来创建新的对象。通过一个ObjectFactory的接口就能完成这个,它只有一个方法,就是“如何创建新的对象”。我们的ObjectFactory接口如下:
public interface ObjectFactory<T> {
T createNew();
}
我们的工具类都已经搞定了,现在可以开始真正实现我们的Pool接口了。因为我们希望这个池能在并发程序里面使用,所以我们会创建一个阻塞的对象池,当没有对象可用的时候,让客户端先阻塞住。我们的阻塞机制是让客户端一直阻塞直到有对象可用为止。这种实现方式导致我们需要再增加一个只阻塞一定时间的方法,如果在超时时间到来前有对象可用则返回,如果超时了就返回null而不是一直等待下去。这样的实现有点类似Java并发库里的LinkedBlockingQueue,因此真正实现前我们再暴露一个接口,BlockingPool,类似于Java并发库里的BlockingQueue接口。
这里是BoundedBlockingPool的声明:
public interface BlockingPool<T> extends Pool<T> {
T get();
T get(long time, TimeUnit unit) throws InterruptedException;
}
public class BoundedBlockingPool<T>
extends AbstractPool<T>
implements BlockingPool<T> {
private int size;
private BlockingQueue<T> objects;
private Validator<T> validator;
private ObjectFactory<T> objectFactory;
private ExecutorService executor = Executors.newCachedThreadPool();
private volatile boolean shutdownCalled;
public BoundedBlockingPool(int size, Validator<T> validator,
ObjectFactory<T> objectFactory) {
super();
this.objectFactory = objectFactory;
this.size = size;
this.validator = validator;
objects = new LinkedBlockingQueue<T>(size);
initializeObjects();
shutdownCalled = false;
}
public T get(long timeOut, TimeUnit unit) {
if (!shutdownCalled) {
T t = null;
try {
t = objects.poll(timeOut, unit);
return t;
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
return t;
}
throw new IllegalStateException("Object pool is already shutdown");
}
public T get() {
if (!shutdownCalled) {
T t = null;
try {
t = objects.take();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
return t;
}
throw new IllegalStateException("Object pool is already shutdown");
}
public void shutdown() {
shutdownCalled = true;
executor.shutdownNow();
clearResources();
}
private void clearResources() {
for (T t : objects) {
validator.invalidate(t);
}
}
@Override
protected void returnToPool(T t) {
if (validator.isValid(t)) {
executor.submit(new ObjectReturner(objects, t));
}
}
@Override
protected void handleInvalidReturn(T t) {
}
@Override
protected boolean isValid(T t) {
return validator.isValid(t);
}
private void initializeObjects() {
for (int i = 0; i < size; i++) {
objects.add(objectFactory.createNew());
}
}
private class ObjectReturner<E> implements Callable<Void> {
private BlockingQueue<E> queue;
private E e;
public ObjectReturner(BlockingQueue<E> queue, E e) {
this.queue = queue;
this.e = e;
}
public Void call() {
while (true) {
try {
queue.put(e);
break;
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
return null;
}
}
}
上面是一个非常基本的对象池,它内部是基于一个LinkedBlockingQueue来实现的。这里唯一比较有意思的方法就是returnToPool。因为内部的存储是一个LinkedBlockingQueue实现的,如果我们直接把返回的对象扔进去的话,如果队列已满可能会阻塞住客户端。不过我们不希望客户端因为把对象放回池里这么个普通的方法就阻塞住了。所以我们把最终将对象插入到队列里的任务作为一个异步的的任务提交给一个Executor来执行,以便让客户端线程能立即返回。
现在我们将在自己的代码中使用上面这个对象池,用它来缓存数据库连接。我们需要一个校验器来验证数据库连接是否有效。
下面是这个JDBCConnectionValidator:
public final class JDBCConnectionValidator implements Pool.Validator<Connection> {
public boolean isValid(Connection con) {
if (con == null) {
return false;
}
try {
return !con.isClosed();
} catch (SQLException se) {
return false;
}
}
public void invalidate(Connection con) {
try {
con.close();
} catch (SQLException se) {
}
}
}
还有一个JDBCObjectFactory,它将用来生成新的数据库连接对象:
public class JDBCConnectionFactory implements ObjectFactory<Connection> {
private String connectionURL;
private String userName;
private String password;
public JDBCConnectionFactory(String driver, String connectionURL,
String userName, String password) {
super();
try {
Class.forName(driver);
} catch (ClassNotFoundException ce) {
throw new IllegalArgumentException(
"Unable to find driver in classpath", ce);
}
this.connectionURL = connectionURL;
this.userName = userName;
this.password = password;
}
public Connection createNew() {
try {
return DriverManager.getConnection(
connectionURL, userName, password);
} catch (SQLException se) {
throw new IllegalArgumentException(
"Unable to create new connection", se);
}
}
}
现在我们用上述的Validator和ObjectFactory来创建一个JDBC的连接池:
public static void main(String[] args) {
Pool<Connection> pool = new BoundedBlockingPool<Connection>(
10,
new JDBCConnectionValidator(),
new JDBCConnectionFactory("", "", "", "")
);
//do whatever you like
}