对象池技术

Apache的commons-pool提供了编写对象池的API,将用完的对象返回对象池中以便于下次利用,从而减少了对象创建时间。这对于创建对象相对耗时的应用来说,能够提高应用的性能。 


commons-dbcp数据库连接池正是使用commons-pool来创建和数据库之间的连接对象,在对象池中保存这些对象,从而减少了频繁建立连接对象所造成的性能损耗。本文讲述了commons-dbcp是怎样利用commons-pool来建立对象池的。

例如在tomcat中,利用JNDI来查找到资源javax.sql.DataSource的实现,如果使用dbcp连接池,则该实现为org.apache.commons.dbcp.BasicDataSource。我们可以从这个称为“数据源”的类中调用getConnection方法来获得与数据库的连接。但内部是如何实现的呢?对象池在其中占据什么位置呢?这一切对于外部使用者来说是透明的。

BasicDataSource的getConnection首先建立了PoolingDataSource对象来getConnection。这个PoolingDataSource对象中引用了ObjectPool,在getConnection()时,是从ObjectPool中借用了一个对象,既调用ObjectPool.borrowObject()方法。而对于熟悉commons-pool的程序员来说,ObjectPool肯定有与之对应的Factory创建对象,并放到池中。因此dbcp通过实现了接口org.apache.commons.pool.PoolableObjectFactory的类org.apache.commons.dbcp.PoolableConnectionFactory的makeObject方法来创建连接Connection对象。然而PoolableConnectionFactory持有对ConnectionFactory的引用,ConnectionFactory可以有3种策略来创建Connection对象。其中DriverConnectionFactory调用了数据库厂商提供的Driver来获得Connection。


最近在做一个内部测试工具类的优化工作中接触到了连接池, 对象池技术, 将原有的未使用连接池的数据库访问操作改成连接池方式.性能有了非常大的提升, 事实证明, 经过两次改造, 原来一个比较大的测试类需要500多秒, 第一次优化后只需要300多秒, 第二次改用连接池之后同一个测试类只需要80多秒.下面是改造过程中的一些总结.
对象池就是以"空间换时间"的一种常用缓存机制, 这里的"时间"特指创建时间,因此这也给出了对象池的适用范围:如果一种对象的创建过程非常耗时的话, 那么请使用对象池. 内部原理简单的说, 就是将创建的对象放到一个容器中, 用完之后不是销毁而是再放回该容器, 让其他的对象调用, 对象池中还涉及到一些高级的技术, 比如过期销毁, 被破坏时销毁, 对象数超过池大小销毁, 对象池中没有可用空闲对象时等待等等.

apache的common-pool工具库是对池化技术原理的一种具体实现. 在阐述原来之前, 这里先理解几个概念:
对象池(ObjectPool接口): 可以把它认为是一种容器, 它是用来装池对象的, 并且包含了用来创建池对象的工厂对象
池对象:就是要放到池容器中的对象, 理论上可以是任何对象.
对象池工厂(ObjectPoolFactory接口):用来创建对象池的工厂, 这个没什么好说的.
池对象工厂(PoolableObjectFactory接口):用来创建池对象, 将不用的池对象进行钝化(passivateObject), 对要使用的池对象进行激活(activeObject), 对池对象进行验证(validateObject),对有问题的池对象进行销毁(destroyObject)等工作

对象池中封装了创建, 获取, 归还, 销毁池对象的职责, 当然这些工作都是通过池对象工厂来实施的, 容器内部还有一个或多个用来盛池对象的容器.对象池会对容器大小, 存放时间, 访问等待时间, 空闲时间等等进行一些控制, 因为可以根据需要来调整这些设置.

当需要拿一个池对象的时候, 就从容器中取出一个, 如果容器中没有的话, 而且又没有达到容器的最大限制, 那么就调用池对象工厂, 新建一个池对象, 并调用工厂的激活方法, 对创建的对象进行激活, 验证等一系列操作. 如果已经达到池容器的最大值, 而对象池中又经没有空闲的对象, 那么将会继续等待, 直到有新的空闲的对象被丢进来, 当然这个等待也是有限度的, 如果超出了这个限度, 对象池就会抛出异常.

"出来混, 总是要还的",池对象也是如此, 当将用完的池对象归还到对象池中的时候, 对象池会调用池对象工厂对该池对象进行验证, 如果验证不通过则被认为是有问题的对象, 将会被销毁, 同样如果容器已经满了, 这个归还池对象将变的"无家可归", 也会被销毁, 如果不属于上面两种情况, 对象池就会调用工厂对象将其钝化并放入容器中. 在整个过程中, 激活, 检查, 钝化处理都不是必须的, 因此我们在实现PoolableObjectFactory接口的时候, 一般不作处理, 给空实现即可, 所以诞生了BasePoolableObjectFactory.

当然你也可以将要已有的对象创建好, 然后通过addObject放到对象池中去, 以备后用.

为了确保对对象池的访问都是线程安全的, 所有对容器的操作都必须放在synchronized
在apache的common-pool工具库中有5种对象池:GenericObjectPool和GenericKeyedObjectPool,SoftReferenceObjectPool, StackObjectPool, StackKeyedObjectPool.
五种对象池可分为两类, 一类是无key的:

另一类是有key的:


前面两种用CursorableLinkedList来做容器,SoftReferenceObjectPool用ArrayList做容器, 一次性创建所有池化对象, 并对容器中的对象进行了软引用(SoftReference)处理, 从而保证在内存充足的时候池对象不会轻易被jvm垃圾回收, 从而具有很强的缓存能力. 最后两种用Stack做容器. 不带key的对象池是对前面池技术原理的一种简单实现, 带key的相对复杂一些, 它会将池对象按照key来进行分类, 具有相同的key被划分到一组类别中, 因此有多少个key, 就会有多少个容器. 之所以需要带key的这种对象池, 是因为普通的对象池通过makeObject()方法创建的对象基本上都是一模一样的, 因为没法传递参数来对池对象进行定制. 因此四种池对象的区别主要体现在内部的容器的区别, Stack遵循"后进先出"的原则并能保证线程安全, CursorableLinkedList是一个内部用游标(cursor)来定位当前元素的双向链表, 是非线程安全的, 但是能满足对容器的并发修改.ArrayList是非线程安全的, 便利方便的容器.

使用对象池的一般步骤:创建一个池对象工厂, 将该工厂注入到对象池中,当要取池对象, 调用borrowObject, 当要归还池对象时, 调用returnObject, 销毁池对象调用clear(),如果要连池对象工厂也一起销毁, 则调用close().
下面是一些时序图:
borrowObject:



returnObject:



invalidateObject:


apache的连接池工具库common-dbcp是common-pool在数据库访问方面的一个具体应用.当对common-pool熟悉之后, 对common-dbcp就很好理解了. 它通过对已有的Connection, Statment对象包装成池对象PoolableConnection,PoolablePreparedStatement. 然后在这些池化的对象中, 持有一个对对象池的引用,在关闭的时候, 不进行真正的关闭处理, 而是通过调用:

 

1.       _pool.returnObject(this);  

_pool.returnObject(this);


或:

 

1.       _pool.returnObject(_key,this);  

 _pool.returnObject(_key,this);

这样一句, 将连接对象放回连接池中.
而对应的对象池前者采用的是ObjectPool, 后者是KeyedObjectPool,因为一个数据库只对应一个连接, 而执行操作的Statement却根据Sql的不同会分很多种. 因此需要根据sql语句的不同多次进行缓存
在对连接池的管理上, common-dbcp主要采用两种对象:
一个是PoolingDriver, 另一个是PoolingDataSource,二者的区别是PoolingDriver是一个更底层的操作类, 它持有一个连接池映射列表, 一般针对在一个jvm中要连接多个数据库, 而后者相对简单一些. 内部只能持有一个连接池, 即一个数据源对应一个连接池.
下面是common-dbcp的结构关系:


下面是参考了common-dbcp的例子之后写的一个从连接池中获取连接的工具类

 

1.       /** 

2.        * 创建连接  

3.        *  

4.        * @since 2009-1-22 下午02:58:35  

5.        */  

6.       public class ConnectionUtils {  

7.           // 一些common-dbcp内部定义的protocol  

8.           private static final String POOL_DRIVER_KEY = "jdbc:apache:commons:dbcp:";  

9.           private static final String POLLING_DRIVER = "org.apache.commons.dbcp.PoolingDriver";  

10.       

11.         /**  

12.          * 取得池化驱动器  

13.          *   

14.          * @return 

15.          * @throws ClassNotFoundException 

16.          * @throws SQLException 

17.          */  

18.         private static PoolingDriver getPoolDriver() throws ClassNotFoundException,  

19.                 SQLException {  

20.             Class.forName(POLLING_DRIVER);  

21.             return (PoolingDriver) DriverManager.getDriver(POOL_DRIVER_KEY);  

22.         }   

23.       

24.         /**  

25.          * 销毁所有连接  

26.          *   

27.          * @throws Exception 

28.          */  

29.         public static void destory() throws Exception {  

30.             PoolingDriver driver = getPoolDriver();  

31.             String[] names = driver.getPoolNames();  

32.             for (String name : names) {  

33.                 driver.getConnectionPool(name).close();  

34.             }  

35.         }   

36.       

37.         /**  

38.          * 从连接池中获取数据库连接  

39.          */  

40.         public static Connection getConnection(TableMetaData table)  

41.                 throws Exception {  

42.             String key = table.getConnectionKey();  

43.       

44.             PoolingDriver driver = getPoolDriver();  

45.       

46.             ObjectPool pool = null;  

47.             // 这里找不到连接池会抛异常, 需要catch一下  

48.             try {  

49.                 pool = driver.getConnectionPool(key);  

50.             } catch (Exception e) {  

51.             }  

52.               

53.             if (pool == null) {  

54.                 // 根据数据库类型构建连接工厂   

55.                 ConnectionFactory connectionFactory = null;  

56.                 if (table.getDbAddr() != null  

57.                         && TableMetaData.DB_TYPE_MYSQL == table.getDbType()) {  

58.                     Class.forName(TableMetaData.MYSQL_DRIVER);  

59.                     connectionFactory = new DriverManagerConnectionFactory(table  

60.                             .getDBUrl(), null);  

61.                 } else {  

62.                     Class.forName(TableMetaData.ORACLE_DRIVER);  

63.                     connectionFactory = new DriverManagerConnectionFactory(table  

64.                             .getDBUrl(), table.getDbuser(), table.getDbpass());  

65.                 }  

66.                   

67.                 // 构造连接池   

68.                 ObjectPool connectionPool = new GenericObjectPool(null);  

69.                 new PoolableConnectionFactory(connectionFactory, connectionPool,  

70.                         null, null, false, true);  

71.                   

72.                 // 将连接池注册到driver中   

73.                 driver.registerPool(key, connectionPool);  

74.             }  

75.       

76.             // 从连接池中拿一个连接   

77.             return DriverManager.getConnection(POOL_DRIVER_KEY + key);  

78.         }   

79.       

80.     }  

Java对象的生命周期大致包括三个阶段:对象的创建,对象的使用,对象的清除。因此,对象的生命周期长度可用如下的表达式表示:T = T1 + T2 +T3。其中T1表示对象的创建时间,T2表示对象的使用时间,而T3则表示其清除时间。由此,我们可以看出,只有T2是真正有效的时间,而T1、T3则 是对象本身的开销。下面再看看T1、T3在对象的整个生命周期中所占的比例。

  我们知道,Java对象是通过构造函数来创建的,在这一过程中,该构造函数链中的所有构造函数也都会被自动调用。另外,默认情况下,调用类的构造函数 时,Java会把变量初始化成确定的值:所有的对象被设置成null,整数变量(byte、short、int、long)设置成0,float和 double变量设置成0.0,逻辑值设置成false。所以用new关键字来新建一个对象的时间开销是很大的,如表1所示。

  表1 一些操作所耗费时间的对照表

  运算操作 示例 标准化时间

  本地赋值 i = n 1.0

  实例赋值 this.i = n 1.2

  方法调用 Funct() 5.9

  新建对象 New Object() 980

  新建数组 New int[10] 3100

  从表1可以看出,新建一个对象需要980个单位的时间,是本地赋值时间的980倍,是方法调用时间的166倍,而若新建一个数组所花费的时间就更多了。

  再看清除对象的过程。我们知道,Java语言的一个优势,就是Java程序员勿需再像C/C++程序员那样,显式地释放对象,而由称为垃圾收集器 (Garbage Collector)的自动内存管理系统,定时或在内存凸现出不足时,自动回收垃圾对象所占的内存。凡事有利总也有弊,这虽然为Java程序设计者提供了 极大的方便,但同时它也带来了较大的性能开销。这种开销包括两方面,首先是对象管理开销,GC为了能够正确释放对象,它必须监控每一个对象的运行状态,包 括对象的申请、引用、被引用、赋值等。其次,在GC开始回收“垃圾”对象时,系统会暂停应用程序的执行,而独自占用CPU。

  因此,如果要改善应用程序的性能,一方面应尽量减少创建新对象的次数;同时,还应尽量减少T1、T3的时间,而这些均可以通过对象池技术来实现。

  对象池技术的基本原理

  对象池技术基本原理的核心有两点:缓存和共享,即对于那些被频繁使用的对象,在使用完后,不立即将它们释放,而是将它们缓存起来,以供后续的应用程序重 复使用,从而减少创建对象和释放对象的次数,进而改善应用程序的性能。事实上,由于对象池技术将对象限制在一定的数量,也有效地减少了应用程序内存上的开 销。

  实现一个对象池,一般会涉及到如下的类:

  1)对象池工厂(ObjectPoolFactory)类

  该类主要用于管理相同类型和设置的对象池(ObjectPool),它一般包含如下两个方法:

  ·createPool:用于创建特定类型和设置的对象池;

  ·destroyPool:用于释放指定的对象池;

  同时为保证ObjectPoolFactory的单一实例,可以采用Singleton设计模式,见下述getInstance方法的实现:

  public static ObjectPoolFactorygetInstance() {

  if (poolFactory == null) {

  poolFactory = new ObjectPoolFactory();

  }

  return poolFactory;

  }

  2)参数对象(ParameterObject)类

  该类主要用于封装所创建对象池的一些属性参数,如池中可存放对象的数目的最大值(maxCount)、最小值(minCount)等。

  3)对象池(ObjectPool)类

  用于管理要被池化对象的借出和归还,并通知PoolableObjectFactory完成相应的工作。它一般包含如下两个方法:

  ·getObject:用于从池中借出对象;

  ·returnObject:将池化对象返回到池中,并通知所有处于等待状态的线程;

  4)池化对象工厂(PoolableObjectFactory)类

  该类主要负责管理池化对象的生命周期,就简单来说,一般包括对象的创建及销毁。该类同ObjectPoolFactory一样,也可将其实现为单实例。

  通用对象池的实现

  对象池的构造和管理可以按照多种方式实现。最灵活的方式是将池化对象的Class类型在对象池之外指定,即在ObjectPoolFactory类创建对象池时,动态指定该对象池所池化对象的Class类型,其实现代码如下:

  . . .

  public ObjectPool createPool(ParameterObjectparaObj,Class clsType) {

  return new ObjectPool(paraObj, clsType);

  }

  . . .

  其中,paraObj参数用于指定对象池的特征属性,clsType参数则指定了该对象池所存放对象的类型。对象池(ObjectPool)创建以后,下面就是利用它来管理对象了,具体实现如下:

  public class ObjectPool {

  private ParameterObject paraObj;//该对象池的属性参数对象

  private Class clsType;//该对象池中所存放对象的类型

  private int currentNum = 0; //该对象池当前已创建的对象数目

  private Object currentObj;//该对象池当前可以借出的对象

  private Vector pool;//用于存放对象的池

  public ObjectPool(ParameterObject paraObj,Class clsType) {

  this.paraObj = paraObj;

  this.clsType = clsType;

  pool = new Vector();

  }

  public Object getObject() {

  if (pool.size() < =paraObj.getMinCount()) {

  if (currentNum < = paraObj.getMaxCount()){

  //如果当前池中无对象可用,而且已创建的对象数目小于所限制的最大值,就利用

  //PoolObjectFactory创建一个新的对象

  PoolableObjectFactory objFactory=PoolableObjectFactory.getInstance();

  currentObj = objFactory.create Object(clsType);

  currentNum++;

  } else {

  //如果当前池中无对象可用,而且所创建的对象数目已达到所限制的最大值,

  //就只能等待其它线程返回对象到池中

  synchronized (this) {

  try {

  wait();

  } catch (InterruptedException e) {

  System.out.println(e.getMessage());

  e.printStackTrace();

  }

  currentObj = pool.firstElement();

  }

  }

  } else {

  //如果当前池中有可用的对象,就直接从池中取出对象

  currentObj = pool.firstElement();

  }

  return currentObj;

  }

  public void returnObject(Object obj) {

  // 确保对象具有正确的类型

  if (obj.isInstance(clsType)) {

  pool.addElement(obj);

  synchronized (this) {

  notifyAll();

  }

  } else {

  throw new IllegalArgumentException( "该对象池不能存放指定的对象类型");

  }

  }

  }

  从上述代码可以看出,ObjectPool利用一个java.util.Vector作为可扩展的对象池,并通过它的构造函数来指定池化对象的 Class类型及对象池的一些属性。在有对象返回到对象池时,它将检查对象的类型是否正确。当对象池里不再有可用对象时,它或者等待已被使用的池化对象返 回池中,或者创建一个新的对象实例。不过,新对象实例的创建并不在ObjectPool类中,而是由PoolableObjectFactory类的 createObject方法来完成的,具体实现如下:

  . . .

  public Object createObject(Class clsType) {

  Object obj = null;

  try {

  obj = clsType.newInstance();

  } catch (Exception e) {

  e.printStackTrace();

  }

  return obj;

  }

  . . .

  这样,通用对象池的实现就算完成了,下面再看看客户端(Client)如何来使用它,假定池化对象的Class类型

为StringBuffer:

  . . .

  //创建对象池工厂

  ObjectPoolFactory poolFactory =ObjectPoolFactory. getInstance ();

  //定义所创建对象池的属性

  ParameterObject paraObj = new ParameterObject(2,1);

  //利用对象池工厂,创建一个存放StringBuffer类型对象的对象池

  ObjectPool pool =poolFactory.createPool(paraObj,String Buffer.class);

  //从池中取出一个StringBuffer对象

  StringBuffer buffer =(StringBuffer)pool.getObject();

  //使用从池中取出的StringBuffer对象

  buffer.append("hello");

  System.out.println(buffer.toString());

  . . .

  可以看出,通用对象池使用起来还是很方便的,不仅可以方便地避免频繁创建对象的开销,而且通用程度高。但遗憾的是,由于需要使用大量的类型定型 (cast)操作,再加上一些对Vector类的同步操作,使得它在某些情况下对性能的改进非常有限,尤其对那些创建周期比较短的对象。

  专用对象池的实现

  由于通用对象池的管理开销比较大,某种程度上抵消了重用对象所带来的大部分优势。为解决该问题,可以采用专用对象池的方法。即对象池所池化对象的 Class类型不是动态指定的,而是预先就已指定。这样,它在实现上也会较通用对象池简单些,可以不要ObjectPoolFactory和 PoolableObjectFactory类,而将它们的功能直接融合到ObjectPool类,具体如下(假定被池化对象的Class类型仍为StringBuffer,而用省略号表示的地方,表示代码同通用对象池的实现):

  public class ObjectPool {

  private ParameterObject paraObj;//该对象池的属性参数对象

  private int currentNum = 0; //该对象池当前已创建的对象数目

  private StringBuffer currentObj;//该对象池当前可以借出的对象

  private Vector pool;//用于存放对象的池

  public ObjectPool(ParameterObject paraObj) {

  this.paraObj = paraObj;

  pool = new Vector();

  }

  public StringBuffer getObject() {

  if (pool.size() < =paraObj.getMinCount()) {

  if (currentNum < = paraObj.getMaxCount()){

  currentObj = new StringBuffer();

  currentNum++;

  }

  . . .

  }

  return currentObj;

  }

  public void returnObject(Object obj) {

  // 确保对象具有正确的类型

  if (StringBuffer.isInstance(obj)) {

  . . .

  }

  }

  结束语

  恰当地使用对象池技术,能有效地改善应用程序的性能。目前,对象池技术已得到广泛的应用,如对于网络和数据库连接这类重量级的对象,一般都会采用对象池技术。但在使用对象池技术时也要注意如下问题:

  ·并非任何情况下都适合采用对象池技术。基本上,只在重复生成某种对象的操作成为影响性能的关键因素的时候,才适合采用对象池技术。而如果进行池化所能带来的性能提高并不重要的话,还是不采用对象池化技术为佳,以保持代码的简明。

·要根据具体情况正确选择对象池的实现方式。如果是创建一个公用的对象池技术实现包,或需要在程序中动态指定所池化对象的Class类型时,才选择通用对象池。而大部分情况下,采用专用对象池就可以了。

 

对象池

  COM+ 对象池服务可以减少从头创建每个对象的系统开销。在激活对象时,它从池中提取。在停用对象时,它放回池中,等待下一个请求。

  对象池使您能够控制所使用的连接数量,与连接池相反,连接池用来控制达到的最大数量。下面是对象池和连接池之间的重要区别:

  创建

  使用连接池时,创建在同一线程上进行,因此如果池中没有连接,则代表您创建连接。采用对象池时,池可以决定创建新对象。但是,如果已经达到最大数量,它会给您下一个可用的对象。当需要花费较长时间来创建对象时,这的确是一个重要的行为。但不要长期使用这种方法来创建对象。

  最小值和最大值的实施

  不在连接池中实施。对象池的最大值在尝试缩放应用程序时很重要。可能需要仅为几个对象而复用成千上万个请求。(TPC/C 基准依赖这一功能。)

  COM+ 对象池与 .NETFramework 托管 SQL 客户端连接池几乎完全相同。例如,创建在不同的线程上进行,并强制实施最小值和最大值。

  注意

应用程序域会影响对象池的行为。在 Microsoft Windows 2000 中,当应用程序激活设置为Library 并且您有多个应用程序域时,将在默认应用程序域中创建所有缓冲池对象,并在多个客户端之间共享。在同样的情况下,当使用 Microsoft Windows XP 和 Windows Server 2003 时,每个应用程序域都有一个对象池。无论使用哪一种操作系统,当有多个应用程序域,且应用程序激活设置为服务器时,进程外客户端将使用默认应用程序域中的对象池。

 

连接池

连接池的好处

  连接池是创建和管理一个连接的缓冲池的技术,这些连接准备好被任何需要它们的线程使用。

  这种把连接“汇集”起来的技术基于这样的一个事实:对于大多数应用程序,当它们正在处理通常需要数毫秒完成的事务时,仅需要能够访问JDBC 连接的 1 个线程。当不处理事务时,这个连接就会闲置。相反,连接池允许闲置的连接被其它需要的线程使用。

  事实上,当一个线程需要用 JDBC 对一个 GBase 或其它数据库操作时,它从池中请求一个连接。当这个线程使用完了这个连接,将它返回到连接池中,这样这就可以被其它想使用它的线程使用。

  当连接从池中“借出”,它被请求它的线程专有地使用。从编程的角度来看,这和用户的线程每当需要一个 JDBC 连接的时候调用DriverManager.getConnection()是一样的,采用连接池技术,可通过使用新的或已有的连接结束线程。

  连接池可以极大的改善用户的 Java 应用程序的性能,同时减少全部资源的使用。连接池主要的优点有:

  减少连接创建时间

  虽然与其它数据库相比 GBase 提供了较为快速连接功能,但是创建新的 JDBC 连接仍会招致网络和JDBC 驱动的开销。如果这类连接是“循环”使用的,使用该方式这些花销就可避免。

  简化的编程模式

  当使用连接池时,每一个单独的线程能够像创建了一个自己的 JDBC 连接一样操作,允许用户直接使用JDBC编程技术

  受控的资源使用

  如果用户不使用连接池,而是每当线程需要时创建一个新的连接,那么用户的应用程序的资源使用会产生非常大的浪费并且可能会导致高负载下的异常发生。

  注意,每个连到 GBase 的连接在客户端和服务器端都有花销(内存,CPU,上下文切换等等)。每个连接均会对应用程序和 GBase 服务器的可用资源带来一定的限制。不管这些连接是否在做有用的工作,仍将使用这些资源中的相当一部分。

  连接池能够使性能最大化,同时还能将资源利用控制在一定的水平之下,如果超过该水平,应用程序将崩溃而不仅仅是变慢。

连接池运作原理

  在实际应用开发中,特别是在WEB应用系统中,如果JSP、Servlet或EJB使用JDBC直接访问数据库中的数据,每一次数据访问请求都必须经历建立数据库连接、打开数据库、存取数据和关闭数据库连接等步骤,而连接并打开数据库是一件既消耗资源又费时的工作,如果频繁发生这种数据库操作,系统的性能必然会急剧下降,甚至会导致系统崩溃。数据库连接池技术是解决这个问题最常用的方法,在许多应用程序服务器(例如:Weblogic,WebSphere,JBoss)中,基本都提供了这项技术,无需自己编程,但是,深入了解这项技术是非常必要的。

  数据库连接池技术的思想非常简单,将数据库连接作为对象存储在一个Vector对象中,一旦数据库连接建立后,不同的数据库访问请求就可以共享这些连接,这样,通过复用这些已经建立的数据库连接,可以克服上述缺点,极大地节省系统资源和时间。

  数据库连接池的主要操作如下:

  (1)建立数据库连接池对象(服务器启动)。

  (2)按照事先指定的参数创建初始数量的数据库连接(即:空闲连接数)。

  (3)对于一个数据库访问请求,直接从连接池中得到一个连接。如果数据库连接池对象中没有空闲的连接,且连接数没有达到最大(即:最大活跃连接数),创建一个新的数据库连接。

  (4)存取数据库。

  (5)关闭数据库,释放所有数据库连接(此时的关闭数据库连接,并非真正关闭,而是将其放入空闲队列中。如实际空闲连接数大于初始空闲连接数则释放连接)。

  (6)释放数据库连接池对象(服务器停止、维护期间,释放数据库连接池对象,并释放所有连接)。

连接池的实现

  1、连接池模型

  本文讨论的连接池包括一个连接池类(DBConnectionPool)和一个连接池管理类(DBConnetionPoolManager)。连接池类是对某一数据库所有连接的“缓冲池”,主要实现以下功能:①从连接池获取或创建可用连接;②使用完毕之后,把连接返还给连接池;③在系统关闭前,断开所有连接并释放连接占用的系统资源;④还能够处理无效连接(原来登记为可用的连接,由于某种原因不再可用,如超时,通讯问题),并能够限制连接池中的连接总数不低于某个预定值和不超过某个预定值。

  连接池管理类是连接池类的外覆类(wrapper),符合单例模式,即系统中只能有一个连接池管理类的实例。其主要用于对多个连接池对象的管理,具有以下功能:①装载并注册特定数据库的JDBC驱动程序;②根据属性文件给定的信息,创建连接池对象;③为方便管理多个连接池对象,为每一个连接池对象取一个名字,实现连接池名字与其实例之间的映射;④跟踪客户使用连接情况,以便需要时关闭连接释放资源。连接池管理类的引入主要是为了方便对多个连接池的使用和管理,如系统需要连接不同的数据库,或连接相同的数据库但由于安全性问题,需要不同的用户使用不同的名称和密码。

  2、连接池实现

  下面给出连接池类和连接池管理类的主要属性及所要实现的基本接口:

  public class DBConnectionPool implementsTimerListener{

  private int checkedOut;//已被分配出去的连接数

  private ArrayList freeConnections = newArrayList();//容器,空闲池,根据//创建时间顺序存放已创建但尚未分配出去的连接

  private int minConn;//连接池里连接的最小数量

  private int maxConn;//连接池里允许存在的最大连接数

  private String name;//为这个连接池取个名字,方便管理

  private String password;//连接数据库时需要的密码

  private String url;//所要创建连接的数据库的地址

  private String user;//连接数据库时需要的用户名

  public Timer timer;//定时器

  public DBConnectionPool(String name, StringURL, String user, String

  password, int maxConn)//公开的构造函数

  public synchronized voidfreeConnection(Connection con) //使用完毕之后,//把连接返还给空闲池

  public synchronized ConnectiongetConnection(long timeout)//得到一个连接,//timeout是等待时间

  public synchronized void release()//断开所有连接,释放占用的系统资源

  private Connection newConnection()//新建一个数据库连接

  public synchronized void TimerEvent() //定时器事件处理函数

  }

  public class DBConnectionManager {

  static private DBConnectionManagerinstance;//连接池管理类的唯一实例

  static private int clients;//客户数量

  private ArrayList drivers = newArrayList();//容器,存放数据库驱动程序

  private HashMap pools = new HashMap ();//以name/value的形式存取连接池//对象的名字及连接池对象

  static synchronized publicDBConnectionManager getInstance()//如果唯一的//实例instance已经创建,直接返回这个实例;否则,调用私有构造函数,创//建连接池管理类的唯一实例

  private DBConnectionManager()//私有构造函数,在其中调用初始化函数init()

  public void freeConnection(String name,Connection con)// 释放一个连接,//name是一个连接池对象的名字

  public Connection getConnection(Stringname)//从名字为name的连接池对象//中得到一个连接

  public Connection getConnection(String name,long time)//从名字为name

  //的连接池对象中取得一个连接,time是等待时间

  public synchronized void release()//释放所有资源

  private void createPools(Properties props)//根据属性文件提供的信息,创建//一个或多个连接池

  private void init()//初始化连接池管理类的唯一实例,由私有构造函数调用

  private void loadDrivers(Properties props)//装载数据库驱动程序

  }

  3、连接池使用

  上面所实现的连接池在程序开发时如何应用到系统中呢?下面以Servlet为例说明连接池的使用。

  Servlet的生命周期是:在开始建立servlet时,调用其初始化(init)方法。之后每个用户请求都导致一个调用前面建立的实例的service方法的线程。最后,当服务器决定卸载一个servlet时,它首先调用该servlet的 destroy方法。

  根据servlet的特点,我们可以在初始化函数中生成连接池管理类的唯一实例(其中包括创建一个或多个连接池)。如:

  public void init() throws ServletException

  {

  connMgr = DBConnectionManager.getInstance();

  }

  然后就可以在service方法中通过连接池名称使用连接池,执行数据库操作。最后在destroy方法中释放占用的系统资源,如:

  public void destroy() {

  connMgr.release(); super.destroy();

  }

结束语

  在使用JDBC进行与数据库有关的应用开发中,数据库连接的管理是一个难点。很多时候,连接的混乱管理所造成的系统资源开销过大成为制约大型企业级应用效率的瓶颈。对于众多用户访问的Web应用,采用数据库连接技术的系统在效率和稳定性上比采用传统的其他方式的系统要好很多。本文阐述了使用JDBC访问数据库的技术?讨论了基于连接池技术的数据库连接管理的关键问题并给出了一个实现模型。文章所给出的是连接池管理程序的一种基本模式,为提高系统的整体性能,在此基础上还可以进行很多有意义的扩展。

  http://www.webdevelopersjournal.com/columns/connection_pool.html

  http://www.webdevelopersjournal.com/columns/DBConnectionManager.java

 

数据库连接池

数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现得尤为突出。对数据库连接的管理能显著影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标。数据库连接池正是针对这个问题提出来的。数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而再不是重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。

  数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。数据库连接池的最小连接数和最大连接数的设置要考虑到下列几个因素:

1) 最小连接数

  是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费;

2) 最大连接数

  是连接池能申请的最大连接数,如果数据库连接请求超过此数,后面的数据库连接请求将被加入到等待队列中,这会影响之后的数据库操作。

3) 如果最小连接数与最大连接数相差太大,

  那么最先的连接请求将会获利,之后超过最小连接数量的连接请求等价于建立一个新的数据库连接。不过,这些大于最小连接数的数据库连接在使用完不会马上被释放,它将被放到连接池中等待重复使用或是空闲超时后被释放。

Java中开源的数据库连接池

原理

  连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等。也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。

  在Java中开源的数据库连接池有以下几种 :

  1, C3P0 C3P0是一个开放源代码的JDBC连接池,它在lib目录中与Hibernate一起发布,包括了实现jdbc3和jdbc2扩展规范说明的Connection 和Statement 池的DataSources 对象。

  2,Proxool 这是一个Java SQLDriver驱动程序,提供了对你选择的其它类型的驱动程序的连接池封装。可以非常简单的移植到现存的代码中。完全可配置。快速,成熟,健壮。可以透明地为你现存的JDBC驱动程序增加连接池功能。

  3,Jakarta DBCP DBCP是一个依赖Jakarta commons-pool对象池机制的数据库连接池.DBCP可以直接的在应用程序用使用。

  4,DDConnectionBroker DDConnectionBroker是一个简单,轻量级的数据库连接池。

  5,DBPool DBPool是一个高效的易配置的数据库连接池。它除了支持连接池应有的功能之外,还包括了一个对象池使你能够开发一个满足自已需求的数据库连接池。

  6,XAPool XAPool是一个XA数据库连接池。它实现了javax.sql.XADataSource并提供了连接池工具。

  7,Primrose Primrose是一个Java开发的数据库连接池。当前支持的容器包括Tomcat4&5,Resin3与JBoss3.它同样也有一个独立的版本可以在应用程序中使用而不必运行在容器中。Primrose通过一个web接口来控制SQL处理的追踪,配置,动态池管理。在重负荷的情况下可进行连接请求队列处理。

  8,SmartPool SmartPool是一个连接池组件,它模仿应用服务器对象池的特性。SmartPool能够解决一些临界问题如连接泄漏(connection leaks),连接阻塞,打开的JDBC对象如Statements,PreparedStatements等. SmartPool的特性包括支持多个pools,自动关闭相关联的JDBC对象, 在所设定time-outs之后察觉连接泄漏,追踪连接使用情况, 强制启用最近最少用到的连接,把SmartPool"包装"成现存的一个pool等。

  9,MiniConnectionPoolManagerMiniConnectionPoolManager是一个轻量级JDBC数据库连接池。它只需要Java1.5(或更高)并且没有依赖第三方包。

10,BoneCP BoneCP是一个快速,开源的数据库连接池。帮你管理数据连接让你的应用程序能更快速地访问数据库。比C3P0/DBCP连接池快25倍。

 

连接池技术

连接池技术的核心思想是:连接复用,通过建立一个数据库连接池以及一套连接使用、分配、管理策略,使得该连接池中的连接可以得到高效、安全的复用,避免了数据库连接频繁建立、关闭的开销。另外,由于对JDBC中的原始连接进行了封装,从而方便了数据库应用对于连接的使用(特别是对于事务处理),提高了开发效率,也正是因为这个封装层的存在,隔离了应用的本身的处理逻辑和具体数据库访问逻辑,使应用本身的复用成为可能。连接池主要由三部分组成:连接池的建立、连接池中连接的使用管理、连接池的关闭。

连接池的建立

  应用程序中建立的连接池其实是一个静态的。所谓静态连接池是指连接池中的连接在系统初始化时就已分配好,且不能随意关闭连接。Java中提供了很多容器类可以方便的构建连接池,如:Vector、Stack、Servlet、Bean等,通过读取连接属性文件Connections.properties与数据库实例建立连接。在系统初始化时,根据相应的配置创建连接并放置在连接池中,以便需要使用时能从连接池中获取,这样就可以避免连接随意的建立、关闭造成的开销。

连接池的管理

  连接池管理策略是连接池机制的核心。当连接池建立后,如何对连接池中的连接进行管理,解决好连接池内连接的分配和释放,对系统的性能有很大的影响。连接的合理分配、释放可提高连接的复用,降低了系统建立新连接的开销,同时也加速了用户的访问速度。下面介绍连接池中连接的分配、释放策略。

  连接池的分配、释放策略对于有效复用连接非常重要,我们采用的方法是一个很有名的设计模式:Reference Counting(引用记数)。该模式在复用资源方面应用的非常广泛,把该方法运用到对于连接的分配释放上,为每一个数据库连接,保留一个引用记数,用来记录该连接的使用者的个数。具体的实现方法是:

  当客户请求数据库连接时,首先查看连接池中是否有空闲连接(指当前没有分配出去的连接)。如果存在空闲连接,则把连接分配给客户并作相应处理(即标记该连接为正在使用,引用计数加1)。如果没有空闲连接,则查看当前所开的连接数是不是已经达到maxConn(最大连接数),如果没达到就重新创建一个连接给请求的客户;如果达到就按设定的maxWaitTime(最大等待时间)进行等待,如果等待maxWaitTime后仍没有空闲连接,就抛出无空闲连接的异常给用户。

  当客户释放数据库连接时,先判断该连接的引用次数是否超过了规定值,如果超过就删除该连接,并判断当前连接池内总的连接数是否小于minConn(最小连接数),若小于就将连接池充满;如果没超过就将该连接标记为开放状态,可供再次复用。可以看出正是这套策略保证了数据库连接的有效复用,避免频繁地建立、释放连接所带来的系统资源开销。

连接池的关闭

  当应用程序退出时,应关闭连接池,此时应把在连接池建立时向数据库申请的连接对象统一归还给数据库(即关闭所有数据库连接),这与连接池的建立正好是一个相反过程。

连接池的配置

  数据库连接池中到底要放置多少个连接,才能使系统的性能更佳,用minConn和maxConn来限制。minConn是当应用启动的时候连接池所创建的连接数,如果过大启动将变慢,但是启动后响应更快;如果过小启动加快,但是最初使用的用户将因为连接池中没有足够的连接不可避免的延缓了执行速度。因此应该在开发的过程中设定较小minConn,而在实际应用的中设定较大minConn。maxConn是连接池中的最大连接数,可以通过反复试验来确定此饱和点。为此在连接池类ConnectionPool中加入两个方法getActiveSize()和getOpenSize(),ActiveSize表示某一时间有多少连接正被使用,OpenSize表示连接池中有多少连接被打开,反映了连接池使用的峰值。将这两个值在日志信息中反应出来, minConn的值应该小于平均ActiveSize,而maxConn的值应该在activeSize和OpenSize之间。

连接池的关键技术

事务处理

  前面讨论的是关于使用数据库连接进行普通的数据库访问。对于事务处理,情况就变得比较复杂。因为事务本身要求原则性的保证,此时就要求对于数据库的操作符合"All-All-Nothing"原则,即要么全部完成,要么什么都不做。如果简单的采用上述的连接复用的策略,就会发生问题,因为没有办法控制属于同一个事务的多个数据库操作方法的动作,可能这些数据库操作是在多个连接上进行的,并且这些连接可能被其他非事务方法复用。Connection本身具有提供了对于事务的支持,可以通过设置Connection的AutoCommit属性为false,显式的调用 commit或rollback方法来实现。但是要安全、高效的进行连接复用,就必须提供相应的事务支持机制。方法是:采用显式的事务支撑方法,每一个事务独占一个连接。这种方法可以大大降低对于事务处理的复杂性,并且又不会妨碍连接的复用。

  连接管理服务提供了显式的事务开始、结束(commit或rollback)声明,以及一个事务注册表,用于登记事务发起者和事务使用的连接的对应关系,通过该表,使用事务的部分和连接管理部分就隔离开,因为该表是在运行时根据实际的调用情况动态生成的。事务使用的连接在该事务运行中不能被复用。在实现中,用户标识是通过使用者所在的线程来标识的。后面的所有对于数据库的访问都是通过查找该注册表,使用已经分配的连接来完成的。当事务结束时,从注册表中删除相应表项。

并发

  为了使连接管理服务有更大的通用性,我们必须要考虑到多线程环境,即并发问题。在一个多线程的环境下,必须要保证连接管理自身数据的一致性和连接内部数据的一致性,在这方面Java提供很好的支持(synchronized关键字),这样就很容易使连接管理成为线程安全的。

多数据库服务器

  在实际应用中,应用程序常常需要访问多个不同的数据库。如何通过同一个连接池访问不同的数据库,是应用程序需要解决的一个核心问题。下面介绍一种解决的途径:

  首先,定义一个数据库连接池参数的类,定义了数据库的JDBC驱动程序类名,连接的URL以及用户名口令等等一些信息,该类是用于初始化连接池的参数:

  public class ConnectionParam implementsSerializable{//各初始化参数的定义}

  其次是连接池的工厂类ConnectionFactory,通过该类将一个连接池对象与一个名称对应起来,使用者通过该名称就可以获取指定的连接池对象,实现的主要代码如下:

  public class ConnectionFactory{staticHashtable connectionPools = //用来保存数据源名和连接池对象的关系public static DataSource lookup(StringdataSourceName) throws

  NameNotFoundException{//查找名字为dataSourceName的数据源}publicstatic DataSource bind(Stringname, ConnectionParam param)

  throws Exception=/将名字name与使用param初始化的连接池对象绑定

  }

  public static void unbind(String name)throws NameNotFound

  Exception{

  //将与名字name绑定的连接池对象删除

  }

连接池应用的实现

  一个完整的连接池应用包括三个部分:DBConnectionPool类,负责从连接池获取(或创建)连接、将连接返回给连接池、系统关闭时关闭所有连接释放所有资源;DBConnectionManager类,负责装载和注册JDBC驱动、根据属性文件中定义的属性创建DBConnectionPool、跟踪应用程序对连接池的引用等;应用程序对连接池的使用。

  本文实现的数据库连接池包括一个管理类DBConnectionManager,负责提供与多个连接池对象(DBConnectionPool类)之间的接口。每一个连接池对象管理一组封装过的JDBC连接对象Conn,封装过的JDBC连接对象Conn可以被任意数量的Model层的组件共享。

  类Conn 的设计很简单,如下所示:

  Class Conn {

  Private java. sgl .Connection con; //数据库连接对象

  Public Boolean inUse ; //是否被使用

  Public long lastAccess; //最近一次释放该连接的时间

  Public int useCount; // 被使用次数

  }

  下面是实现连接池的主要代码:

  // 初始化数据库连接池

  public static synchronized voidFastInitPool()

  throws Exception {

  try { Class.forName(driver);

  for (int i=0; i<size; i++) {

  Connection con = createConnection();

  if (con!=null) addConnection(con);

  } } }

  // 向连接池对象中添加数据库连接

  private static void addConnection(Connectioncon) {

  if (pool=null||pool1=null) {

  pool=new Vector(size);

  pool1=new Vector(size); }

  pool.addElement(con);

  pool1.addElement("false"); }

  // 获取数据库连接

  public static synchronized ConnectiongetConn()

  throws Exception {

  Connection conn = null;

  try { if (driver = null)

  FastInitPool();

  // 获得一个可用的(空闲的)连接

  .for (int i = 0; i < pool.size(); i++) {

  conn = (Connection)pool.elementAt(i);

  if (pool1.elementAt(i)=="false") {

  pool1.set(i,"true");

  //System.out.println("从连接池中获取第"+(i+1)+"个空闲连接");

  return conn;

  }

  }

  //如果没有可用连接,且已有连接数小于最大连接数限制,则创建并增加一个新连接到连接池

  conn = createConnection();

  pool.addElement(conn);

  pool1.addElement("true");

  // System.out.println(" 所有连接都在使用,在连接池中再创建一个新连接");

  }

  catch (Exception e) {

  System.err.println(e.getMessage());

  throw new Exception(e.getMessage());

  }

  return conn; //返回一个有效的新连接

  }

  public Connection getConnection(StringstrDriver, String strUrl, String strUserName, String strPassWord)

  throws SQLException{

  try{ Class.forName(strDriver);

  conn = DriverManager.getConnection(strUrl,strUserName, strPassWord); }

  return conn; }

 

Apache的对象池化工具commons-pool

1.       前言

当我们的应用中创建一个十分最重量级的对象的时候,往往为了节省资源成本,使用单例模式,整个的应用中就只有一个对象供大家使用。这样是节省了不少资源,也是大多数应用的做法。不过如果遇到并发量十分大的情况下,并发访问的问题也就出现了。OK即使您不用加锁synchronized,利用ThreadLoacl进行安全变量的副本,但是维持变量副本的资源也是需要消耗资源的。而且对于一个重量级的对象的多个方法多个线程同时调用此对象的同一个局部变量,多个副本的维护实际上也是挺占用资源的。

2.       轻量级对象

当然不是说所有的对象都适合池化的。池化的对象是一个重量级对象,什么是重量级对象。回答这个问题先说说什么是轻量级对象。比如我们做Web系统的DAO(注意:这里绝对不包括EJB的EAO),就是为了调用持久层框架完成增、删、改、查原子性操作。实际就算是轻量级的对象,DAO没必要含有太复杂的其他对象,如果需要其他辅助对象为其服务:比如事务插入、日志操作记录。使用AOP面向他的切面进行拦截即可。Spring有整套方案,不做扩展。创建这些对象,在启动Web应用的时候实例化放到容器中即可,想要,容器自动为您注入。容器保证了对象的线程安全,所以这些对象是轻量级对象。

3.       重量级对象

那什么又是重量级对象呢,咱们用一个谁都能想到的例子,数据库连接java.sql.Connection。为何说Connection是一个重量级对象呢?我们在JDBC编程的时候仅仅使用了很少量的代码即完成了数据库的连接操作。一个Connection对象为我们操作数据库做了什么?

1):通过JDBC的驱动Driver屏蔽底层数据库网络协议,让我们不必关心具体的数据库通讯协议(Mysql有Mysql的协议、Oracle有Oracle的通讯协议)

2):通过数据库网络协议打开网络socket连接管道

3):根据建立的网络通道发送握手指令(所谓握手指令就是包含数据库用户名、数据库密码的二进制信息)

4):接收数据库端的握手结果信息再决定下面的通讯是否进行

关键是建立网络socket通道耗费资源着实不小。而且java.sql.Connection需要一直维持着这个网络通道。

相比于上面提到的DAO对象的创建,这个Connection对象创建的时候是不是确实显得历经沧桑。咱们举个例子,就像唐僧取经一样,李世民想要经书超度为他建功立业的亡魂,唐三藏就去了,历经千辛万苦取回来了,李世民让其诵念经文超度亡魂后发现这段时间那些死去的冤魂没再缠着他了,取回来的经文这个时候被李世民看经书占地方,下令烧掉了(垃圾回收器回收了)。过了一段时间又有亡魂开始缠着他了!怎么办?李世民说:“哦,御弟,麻烦你再去见佛祖一趟吧!”唐三藏之后又花了12年,再去西天见佛祖,取回了真经超度亡魂~~如果当时唐三藏说:“阿妹喂,陛下,当时您要是不下令烧掉,将经书交给我的大徒弟去管理,就不必这么麻烦~等我取回经书了,您也就快那什么啦”。

4.       对象池

对象池的概念和现实生活中的图书馆的管理极其相似。阅读这项看一本书,用读书卡去借一本书、看完了还回来,如果图书馆的书不够用了怎么办?那就麻烦图书采购小刘去市场买几本回来吧。这个本书太受欢迎了,原始的书的数量不够用啊。对象池就像是一个图书馆,图书管理员就像是对象调度器,借书人就像是调用对象的客户端。需要维护的就是图书馆和借书的记录凭证。这样看来资源需要维护图书馆和借书系统。但是这种维护在某种程度上来说是值得的。回到我们的编程世界中,Apache的common-pool组件就是一个开源的对象池工具,我们可以借助它的接口,就可以将重量级的对象做池化。

5.       环境搭建

首先从apache站点下载commons-pool组件包。将lib下面的jar包拷贝到项目classpath中。因为此实话组件还依赖于其他的apache的commos组件——commons-collections。所以还需要将此jar包加入到classpath。笔者搭建好的环境如下

/ApacheCommonPool/lib/commons-collections-2.1.1.jar

/ApacheCommonPool/lib/commons-pool-1.5.6-javadoc.jar

/ApacheCommonPool/lib/commons-pool-1.5.6-sources.jar

/ApacheCommonPool/lib/commons-pool-1.5.6.jar

简单的池化环境已经搭建完毕

6.       使用例程

在此历程中,我们测试一下Java常用对像,也是最通用的轻量级对象String的池化。再测试一下自定义的实体对象的池化。再次说明一点:String是绝对不适合做池化的,首先java.lang.String对象本身就不是什么重量级对象,初始化String对象也绝对不用经过什么西天取经的过程才能产生出来的,并且JVM已经对String做了池化处理。有一个String池维护已经创建好的字符串引用对象值。在此使用String仅仅是表示如何使用池化操作罢了。实际应用中,千万不要池化String对象。否则就有点得不偿失了。首先给出简单的POJO实体对象代码。

package key;

 

public class Person {

 

         String id;

         String name;

 

         public Person() {

 

         }

 

         public Person(String id, String name) {

                   this.id = id;

                   this.name = name;

         }

 

         public String getId() {

                   return id;

         }

 

         public void setId(String id) {

                   this.id = id;

         }

 

         public String getName() {

                   return name;

         }

 

         public void setName(String name) {

                   this.name = name;

         }

 

         @Override

         public String toString() {

                   return "id:" + id + "---name:" + name;

         }

 

}

这个用户POJO类很简单,就只有id和name属性。

下面是使用池工厂管理程序

/**

 * 池化工厂

 * @author liuyan

 */

class KeyedPoolableObjectFactorySample extends BaseKeyedPoolableObjectFactory {

        

         /**

          * 创建对象方法

          */

         @SuppressWarnings("unchecked")

         public Object makeObject(Object clsName) throws Exception {

 

                   if (clsName == null || !(clsName instanceof String)

                                     || "".equals(clsName)) {

                            throw new RuntimeException("类名为空!");

                   }

 

                   System.out.println("创建一个新的对象:" + clsName);

 

                   Class cls = Class.forName((String) clsName);

                   Object obj = cls.newInstance();

                   return obj;

         }

 

         @Override

         public void activateObject(Object key, Object obj) throws Exception {

                   // TODO Auto-generated method stub

                   super.activateObject(key, obj);

                   System.out.println("激活对象");

         }

 

         @Override

         public void destroyObject(Object key, Object obj) throws Exception {

                   // TODO Auto-generated method stub

                   super.destroyObject(key, obj);

                   System.out.println("销毁对象");

         }

 

         @Override

         public void passivateObject(Object key, Object obj) throws Exception {

                   // TODO Auto-generated method stub

                   super.passivateObject(key, obj);

                   System.out.println("挂起对象");

         }

 

         @Override

         public boolean validateObject(Object key, Object obj) {

                   // TODO Auto-generated method stub

                   System.out.println("验证对象");

                   return super.validateObject(key, obj);

 

         }

 

}

池工厂对象就像是图书馆的调度管理员,需要创建新对象的时候就调用其makeObject()方法;其他的方法已经在程序中进行了说明。主要就是验证对象方法validateObject是验证是否已经存在了可复用的对象。在此历程中采用反射机制对目标对象进行实例化。

下面是使用程序

 

/**

 * 使用

 * @author liuyan

 *

 */

public class KeyedObjectPoolSample {

 

         /**

          * @param args

          */

         public static void main(String[] args) {

 

                   Object obj = null;

                   KeyedPoolableObjectFactory factory = new KeyedPoolableObjectFactorySample();

 

                   KeyedObjectPoolFactory poolFactory = new StackKeyedObjectPoolFactory(

                                     factory);

 

                   KeyedObjectPool pool = poolFactory.createPool();

 

                   String key = null;

 

                   try {

 

                            key = "java.lang.String";

 

                            obj = pool.borrowObject(key);

                            obj = "obj1";

                            // pool.returnObject(key, obj);

                            obj = pool.borrowObject(key);

                            pool.returnObject(key, obj);

                            obj = pool.borrowObject(key);

                            System.out.println(obj);

 

                            System.out.println("============看另一个对象Person=============");

 

                            key = "key.Person";

 

                            Person person1 = (Person) pool.borrowObject(key);

                           

                            person1.setId("1");

                            person1.setName("素还真");

                            System.out.println(person1);

                            pool.returnObject(key, person1);

                            System.out.println(person1);

 

                            Person person2 = (Person) pool.borrowObject(key);

                            person2.setId("2");

                            person2.setName("青阳子");

 

                            Person person3 = (Person) pool.borrowObject(key);

                            person3.setId("3");

                            person3.setName("一页书");

 

                            Person person4 = (Person) pool.borrowObject(key);

                            person4.setId("4");

                            person4.setName("业途灵");

 

                            System.out.println(person1);

                            System.out.println(person2);

                            System.out.println(person3);

                            System.out.println(person4);

                           

                            pool.returnObject(key, person3);

                            Person person5 = (Person) pool.borrowObject(key);

                            System.out.println(person5);

                           

 

                   } catch (Exception e) {

                            e.printStackTrace();

                   } finally {

                            try {

                                     pool.close();

                                     System.out.println(pool);

                            } catch (Exception e) {

                                     e.printStackTrace();

                            }

                   }

 

         }

 

}

首先先建立一个字符串对象,引用值为 “obj1”。接着从对象池中借一个字符串对象出来obj指向这个借出来的对象,若池中有可复用的对象,直接给之,如果没有,调用makeObject()造一个新的对象出来给obj引用。用完后调用returnObject方法将对象还给池子。咱们再看下面使用Person对象,先从池中借一个Person对象person1。当然池子里面现在还没有任何Person对象。对池中对象赋值后,还给了池子。这个时候池子中就有一个被使用过的,并且属性已经发生改变的对象。下面person2变量向池子借了一个Person对象,此时池子里已经有了一个可复用的对象。那么不必调用makeObject方法,直接将已有的对象返回给客户端即可。然后对其赋值。把刚刚的1素还真改成了2青阳子,那么这个时候刚才的person1也是2青阳子,person1的引用指针没变,仅仅是引用的那块区域的字符串值发生了变化。之后还没等这个对象返还给池子呢,person3变量就向池子要对象,池子说:“等等,我给你make一个啊”,之后又调用了markObject方法创建了一个新的对象。这个新的对象就是3一页书。往下的4业途灵也是新的。当执行到

         pool.returnObject(key, person3);

         Person person5 = (Person) pool.borrowObject(key);

         System.out.println(person5);

发现person5是刚刚归还的复用对象——3一页书。执行后控制台如下

创建一个新的对象:java.lang.String

激活对象

验证对象

创建一个新的对象:java.lang.String

激活对象

验证对象

验证对象

挂起对象

激活对象

验证对象

~~~

============看另一个对象Person=============

创建一个新的对象:key.Person

激活对象

验证对象

id:1---name:素还真

验证对象

挂起对象

id:1---name:素还真

激活对象

验证对象

创建一个新的对象:key.Person

激活对象

验证对象

创建一个新的对象:key.Person

激活对象

验证对象

id:2---name:青阳子

id:2---name:青阳子

id:3---name:一页书

id:4---name:业途灵

验证对象

挂起对象

激活对象

验证对象

id:3---name:一页书

org.apache.commons.pool.impl.StackKeyedObjectPool contains 0 distinct pools:

经过这个例子我们还应该清楚一个事实就是像这个可对局部变量访问(get)、赋值(set)的对象也不适合池化。因为别人用过了这样的对象,实际上留下了别人用过的痕迹。这样确实会造成局部变量用起来会不一致的线程安全问题。应该是对外仅仅提供服务的方法,并且不具备访问局部变量的对象,并且创建时十分消耗资源的对象才适合池化。到底是池化还是单例,这个当然没有绝对的答案,一切得看您的实际系统需要怎样做更合理。

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值