作为一个J2EE应用程序开发者,谈到池(pool)我最先想到(也是最早接触)的是数据库连接池,很长一段时间我对池的应用也仅仅局限在数据库链接池, 而且也没有去仔细思考过是什么原因促成我们去使用这项技术——如果你在那时问我为什么要使用池,我会说类似于数据库连接这类对象在创建时需要花费较长的时 间,这时使用池化技术可以有效地进行重复利用池内对象,嗯,一个中规中矩的回答
然而越来越多的人和我说创建对象的开销,已经越来越不足以成为对其进行池化的理由,很遗憾对于这种说法我没法拿出确实的证据去支持或是反驳。但是,在我实际的工作中,我发现池很好地扮演了阀这个角色 ,有趣的是这不是站在我们通常开发的应用程序的角度上说的,而是站在通过池内对象被访问的资源的立场而说的(比如说数据库)——池可以有效的限制应用对某项资源的访问,从而避免对资源的访问超出了资源本身的承受极限 .
说到这里,大家可能觉得很饶(呵呵,汗自己一个 ),一个日常生活中的例子更能说明这个问题——参观者访问博物馆 。
假设大家今天一起去参观一个博物馆,起初,参观者只要在售票处购买了门票就能进入博物馆参观访问,博物馆或是售票处对发放的门票数量不做任何限 制。那么,在参观者数量较少的时候,双方都不会觉得这种方式有什么不妥——参观者的访问需求被满足,博物馆提供了应有的服务,而门票很好地充当了凭证这一 角色。但是,我们都知道,一个博物馆所能容纳的参观者数量是有限的,一旦在单位时间内,参观者的数量达到或是超过了这个限额,参观者不得不面对一个人挤人 的场面,参观质量急剧下降;而博物馆可能更不愿意看到这种场面,拥挤对展馆设施和展品是直接的威胁。
现在,博物馆意识到了这个问题,它知会售票处——不能再不管不顾地卖票了,你得承担一个票务中心的职责(类似于我们所说的池),你最多只能发放这 500张可作为门票使用的磁卡,博物馆仅允许持有磁卡的参观者访问,参观者结束访问离开博物馆时,需要将磁卡归还于票务中心,供其发放给其他等候参观的人 员。附件中的“博物馆参观流程1.jpg”简单地描绘了这个参观访问的流程。
对应我们熟悉的数据库连接池的相关应用,我们可以把应用程序中的每个请求视为参观者,数据库就是博物馆了,而数据库连接则是门票(磁卡),数据库 连接池自然是票务中心了。在这里,数据库连接池事先存放这些连接并不仅仅是因为创建需要消耗一定的时间,同时它限制着单位时间内访问数据库的应用程序数量 (站在博物馆的立场,展馆和展品的安全远重要于所发放磁卡的成本,而对参观者来说,使用可循环利用的磁卡作为门票,其参观成本也相应的下降,结果是双赢 的)。
其实这类例子在我们生活中还有很多,比如:
游人去公园租船游湖——游人、码头上出租船的、待租的船、人工湖也适用这种情况,只是在这个场景里,船的制造成本所占的考量比重较博物馆例子中的门票要大。
说到这里,在J2EE应用中,将数据库连接池设为容器资源级别的意义就体现出来了,对于数据库来说,这样做可以为容器内任意访问这个数据库实体的 应用设立一个阀,有效地限制对数据库访问的并发量(容器依然得承担阻塞的代价)。当然,对于容器外可能发生地对数据库的访问,容器就爱莫能助了
说了这么多,下面提供一些例子代码来演示一下“参观者访问博物馆”的问题,例子中对应有参观者(实际每个参观者是一个单个的线程,出于演示目的让 他们反复领票访问)、磁卡、票务中心、博物馆,20名参观者将争夺由票务中心管理的10张磁卡,获取磁卡的将能够进行访问。同时例子代码提供下载,执行 build_ran.bat文件将可直接在控制台观察输出,但建议置入IDE中观察效果更佳。以下是一部分的代码片段,如有出入,还请指正,在此先谢过。
- package demo.pool.resource;
- /**
- * 该类是ICard接口的一个实现,它特定于博物馆
- * 这项资源而设,持有MuseumCard实例的访问者
- * 才能参观博物馆.
- * @author 学无止境
- * @since 2009.04.15
- *
- */
- public class MuseumCard implements ICard {
- /**
- * IResource的博物馆实现,对于访问者,
- * 它提供的服务是参观和游览,当然,这会
- * 消耗访问者一定的时间.在这里我把它作为匿名类
- * 实现是为了避免资源在外部被实例化并暴露引用,
- * 这样做也是为了对应现实中的环境,即具体某个博物馆的存在
- * 客观唯一,而获取有效的凭证是访问者进行参观的唯一途径.
- * 但是需要注意,这是一个特定于Java环境的示例,并且
- * 访问者和资源被置于了一个JVM中,实际应用中情况
- * 有很大的不同.
- */
- private static final IResource museum= new IResource(){
- /**
- * 博物馆将消磨访问者(在本示例中就是当前线程)一定的时光,
- * 以完成一次短暂的参观,在这里我们以输出信息到
- * 控制台的方式来描述每次参观.
- */
- public void service() {
- //由于在这个示例中我将以一个线程的实现来模拟访问者,所以我将以挂起线程2秒
- //的方式来模仿他在博物馆的短暂逗留.但这决不是说,访问者必须是一个线程的
- //实现,而被访问的资源也不见得是由Java实现的,就算是也不见得和访问者
- //存在于一个JVM中.这仅仅是一个示例.
- Thread thread=Thread.currentThread();
- System.out.println(thread.getName()+"进入了博物馆开始参观......" );
- try {
- thread.sleep(2000 );
- }
- catch (InterruptedException e) {
- System.out.println(thread.getName()+"参观博物馆时发生了意外-_-" );
- }
- }
- };
- //当前凭证编号
- private Integer id;
- /**
- * 构造一个新的访问凭证实例,
- * 它将有一个唯一的id作为标识.
- *
- */
- public MuseumCard(Integer id){
- this .id=id;
- }
- /**
- * 持有访问凭证者将能够参观博物馆.
- */
- public void visit() {
- //博物馆仅为持有凭证者提供服务.
- museum.service();
- }
- /**
- * 获取当前凭证的编号
- * @return
- */
- public Integer getId() {
- return id;
- }
- /**
- * 根据凭证的卡号返回凭证描述信息.
- */
- @Override
- public String toString() {
- return "[参观凭证" + this .id+ "]" ;
- }
- }
package demo.pool.resource;
/**
* 该类是ICard接口的一个实现,它特定于博物馆
* 这项资源而设,持有MuseumCard实例的访问者
* 才能参观博物馆.
* @author 学无止境
* @since 2009.04.15
*
*/
public class MuseumCard implements ICard {
/**
* IResource的博物馆实现,对于访问者,
* 它提供的服务是参观和游览,当然,这会
* 消耗访问者一定的时间.在这里我把它作为匿名类
* 实现是为了避免资源在外部被实例化并暴露引用,
* 这样做也是为了对应现实中的环境,即具体某个博物馆的存在
* 客观唯一,而获取有效的凭证是访问者进行参观的唯一途径.
* 但是需要注意,这是一个特定于Java环境的示例,并且
* 访问者和资源被置于了一个JVM中,实际应用中情况
* 有很大的不同.
*/
private static final IResource museum=new IResource(){
/**
* 博物馆将消磨访问者(在本示例中就是当前线程)一定的时光,
* 以完成一次短暂的参观,在这里我们以输出信息到
* 控制台的方式来描述每次参观.
*/
public void service() {
//由于在这个示例中我将以一个线程的实现来模拟访问者,所以我将以挂起线程2秒
//的方式来模仿他在博物馆的短暂逗留.但这决不是说,访问者必须是一个线程的
//实现,而被访问的资源也不见得是由Java实现的,就算是也不见得和访问者
//存在于一个JVM中.这仅仅是一个示例.
Thread thread=Thread.currentThread();
System.out.println(thread.getName()+"进入了博物馆开始参观......");
try {
thread.sleep(2000);
}
catch (InterruptedException e) {
System.out.println(thread.getName()+"参观博物馆时发生了意外-_-");
}
}
};
//当前凭证编号
private Integer id;
/**
* 构造一个新的访问凭证实例,
* 它将有一个唯一的id作为标识.
*
*/
public MuseumCard(Integer id){
this.id=id;
}
/**
* 持有访问凭证者将能够参观博物馆.
*/
public void visit() {
//博物馆仅为持有凭证者提供服务.
museum.service();
}
/**
* 获取当前凭证的编号
* @return
*/
public Integer getId() {
return id;
}
/**
* 根据凭证的卡号返回凭证描述信息.
*/
@Override
public String toString() {
return "[参观凭证"+this.id+"]";
}
}
- package demo.pool.client;
- import org.apache.commons.pool.ObjectPool;
- import demo.pool.resource.ICard;
- /**
- * 一个基于线程的访问者实现,创建它的实例时需要为其注入
- * 一个票务中心(其实是一个存放ICard实例的池),供访问者
- * 申领访问凭证进行访问而用.
- * 另外,我在这里以当前线程名作为访问者的名称,偷懒下^_^
- * @author 学无止境
- * @since 2009.04.15
- *
- */
- public class Visitor implements Runnable {
- private ObjectPool cardCenter;
- /**
- * 在实例化访问者同时,将票务中心的引用注入实例中.
- * @param cardCenter
- */
- public Visitor(ObjectPool cardCenter) {
- this .cardCenter=cardCenter;
- }
- /**
- * 参观者将周而复始地向票务中心(池)
- * 申请凭证以访问博物馆.我用这种方式来
- * 模拟有许多访问者期望参观博物馆.
- */
- public void run() {
- ICard card=null ;
- while ( true ){
- try {
- /*
- * 在为这里加锁前,控制台的输出使我困惑了很久...
- * GenericObjectPool用残酷的事实告诉了我,
- * 别人没承诺这是线程安全的,自己千万别自作多情.
- * 另外,如果你习惯使用Spring的话,那么可以很轻松地
- * (并且是透明地)使用池化对象,但是由于其实现借助了aop,
- * 使得池难以为调用者所感知,并不适合作为示例演示而使用.
- */
- synchronized (cardCenter){
- card=(ICard)cardCenter.borrowObject();
- System.out.println(Thread.currentThread().getName()+"得到了" +card);
- }
- card.visit();
- }
- catch (Exception e) {
- System.out.println(Thread.currentThread().getName()+"在尝试获取凭证并访问博物馆时出现异常[" +e.getMessage()+ "]." );
- }
- finally {
- try {
- if (card!= null ){
- synchronized (cardCenter){
- cardCenter.returnObject(card);
- System.out.println(Thread.currentThread().getName()+"结束参观,离开了博物馆并归还了" +card+ "." );
- card=null ;
- }
- }
- }
- catch (Exception e) {
- System.out.println(Thread.currentThread().getName()+"归还凭证时出现了异常[" +e.getMessage()+ "]." );
- }
- }
- }
- }
- }