2011/6/2Java笔记之对象池2(commons-pool)

创建新的对象并初始化的操作,可能会消耗很多的时间。在这种对象的初始化工作包含了一些费时的操作(例如,从一台位于20,000千米以外的主机上读出一些数据)的时候,尤其是这样。在需要大量生成这样的对象的时候,就可能会对性能造成一些不可忽略的影响。要缓解这个问题,除了选用更好的硬件和更棒的虚拟机以外,适当地采用一些能够减少对象创建次数的编码技巧,也是一种有效的对策。对象池化技术(Object Pooling)就是这方面的著名技巧,而Jakarta Commons Pool组件则是处理对象池化的得力外援。

说明:英语中的Pool除了“池”之外,还有“供多方共享的资源”意思。

对象池化技术

对象池化的基本思路是:将用过的对象保存起来,等下一次需要这种对象的时候,再拿出来重复使用,从而在一定程度上减少频繁创建对象所造成的开销。用于充当保存对象的“容器”的对象,被称为“对象池”(Object Pool,或简称Pool)。

对于没有状态的对象(例如String),在重复使用之前,无需进行任何处理;对于有状态的对象(例如StringBuffer),在重复使用之前,就需要把它们恢复到等同于刚刚生成时的状态。由于条件的限制,恢复某个对象的状态的操作不可能实现了的话,就得把这个对象抛弃,改用新创建的实例了。

并非所有对象都适合拿来池化――因为维护对象池也要造成一定开销。对生成时开销不大的对象进行池化,反而可能会出现“维护对象池的开销”大于“生成新对象的开销”,从而使性能降低的情况。但是对于生成时开销可观的对象,池化技术就是提高性能的有效策略了。

 

项目代码示例详见:DefaultAvFlightSearch.searchFlightInAbacus()方法,AbacusSecurityHolderObjectPool类,AbacusSecurityHolderObjectFactory类

Objectpool就是一个管理对象的池子。

主要就靠3个接口来管理:

ObjectPool, 定义了池接口,就是说,以后的对象池,至少模子是这个样子的~~主要两个实现抽象类:BaseObjectPool和KeyedObjectPool。有一些基本方法比如从对象池取对象,调用borrowObject()方法,将对象放回使用returnObject()方法。清空选择clear(),而不需要对象池选择关闭时使用close()等。在具体的Pool实现中,会依赖PoolableObjectFactory接口。

PoolableObjectFactory,怎么理解这个接口呢?其实在ObjectPool中我们看到只是声明了一些调用和放回对象的方法,可以理解为真正我们在操作一个pool,这就是一个数据结构。然而pool中的对象创建、管理、销毁等事务pool管不了了。因此框架又定义了这样一个factory去管理这些对象。该接口有makeObject()方法,我们自己的factory实现里可以利用这个方法去创建个性化的对象。有创建就有销毁,destroyObject(Object obj)方法就用来销毁对象。同时还有activateObject(Object obj)方法用来激活对象,也就是说令对象可用。有passivateObject(Object obj)方法用来将对象作为idle状态。同时还有validateObject(Object obj)方法来检验对象是否可以由pool安全返回。

ObjectPoolFactory,作为最后一个接口,我感觉这个factory应该算是用处不特别大吧。顾名思义,用来管理pool的一个factory。我们的应用如果需要的话,可能不止一个pool。由一个factory去管理,明显合理的,而且factory模式也是pool框架的特色。该接口只有一个方法,那就是createObjectPool()。

 

框架的类图主要关系就是这样

clip_image002

首先定义一个对象(一般使用线程池的原则是对象比较大,所以我们就叫它大对象):

  10: public class BigObject {
  12:     private boolean active; 
  14:     public boolean isActive() {
  15:         return active;
  16:     }
  18:     public void setActive(boolean active) {
  19:         this.active = active;
  20:     }
  22:     public BigObject() {
  23:         active = true;
  24:         System.out.println("i am a big object!!!");
  25:     }
  27:     public String toString() {
  28:         return "big object "+ this.hashCode();
  29:     }
  30: }

接着去实现PoolableObjectFactory:

  11: public class TestFactory implements PoolableObjectFactory{ 
  13:     @Override
  14:     public void activateObject(Object arg0) throws Exception {
 
  16:         ((BigObject)arg0).setActive(true);
  17:     }
  19:     @Override
  20:     public void destroyObject(Object arg0) throws Exception {
  22:         arg0 = null;
  23:     }
  25:     @Override
  26:     public Object makeObject() throws Exception {
  28:         BigObject bo = new BigObject();
  29:         return bo;
  30:     }
  32:     @Override
  33:     public void passivateObject(Object arg0) throws Exception {
  35:         ((BigObject)arg0).setActive(false);
  36:     }
  38:     @Override
  39:     public boolean validateObject(Object arg0) {
  41:         if(((BigObject)arg0).isActive())
  42:             return true;
  43:         else
  44:             return false;
  45:     }
  46:  
  47: }

最后测试:

  15: public class PoolTest {
  20:     public static void main(String[] args) {
  22:         BigObject bo = null;
  23:         PoolableObjectFactory factory = new TestFactory();
  24:         ObjectPool pool = new StackObjectPool(factory);
  25:         try {
  26:             for(long i = 0; i < 10 ; i++) {
  27:                 System.out.println("== " + i + " ==");
  28:                 bo = (BigObject) pool.borrowObject();
  29:                 System.out.println(bo);
  30:                 System.out.println(pool.getNumActive());
  31:                 if((i&1)==0) {
  32:                     pool.returnObject(bo);
  33:                 }
  34:             }
  36:         }
  37:         catch (Exception e) {
  38:             e.printStackTrace();
  39:         }
  40:         finally {
  41:             try{
  42:                 if (bo != null) {//避免将一个对象归还两次
  43:                     pool.returnObject(bo);
  44:                 }
  45:                 pool.close();
  46:             }
  47:             catch (Exception e){
  48:                 e.printStackTrace();
  49:             }
  50:         }
  52:     }
  54: }

结束了,一个pool的简单应用就搞定了。下面我们看看代码的具体实现是什么。

先看看pool构造时候都做了些什么:

StackObjectPool里有两个protected field,一个protected PoolableObjectFactory _factory = null;一个protected Stack _pool = null;。前者是通过构造方法注入的一个factory,我们的代码里已经自己实现了一个TestFactory;后者是具体的Pool管理策略,像StackObjectPool的话就是内部用这样一个Stack来管理对象。

构造时会通过

   1: public StackObjectPool(PoolableObjectFactory factory, int maxIdle, int initIdleCapacity) {
   2:         _factory = factory;
   3:         _maxSleeping = (maxIdle < 0 ? DEFAULT_MAX_SLEEPING : maxIdle);
   4:         int initcapacity = (initIdleCapacity < 1 ? DEFAULT_INIT_SLEEPING_CAPACITY : initIdleCapacity);
   5:         _pool = new Stack();
   6:         _pool.ensureCapacity( initcapacity > _maxSleeping ? _maxSleeping : initcapacity);
   7:     }

来完成初始参数和变量的构造。

接着我们开始从pool中取对象了,调用borrowObject的方法,具体实现在源码中是这样的:

   1: public synchronized Object borrowObject() throws Exception {
   2:         assertOpen();
   3:         Object obj = null;
   4:         boolean newlyCreated = false;
   5:         while (null == obj) {
   6:             if (!_pool.empty()) {
   7:                 obj = _pool.pop();
   8:             } else {
   9:                 if(null == _factory) {
  10:                     throw new NoSuchElementException();
  11:                 } else {
  12:                     obj = _factory.makeObject();
  13:                     newlyCreated = true;
  14:                   if (obj == null) {
  15:                     throw new NoSuchElementException("PoolableObjectFactory.makeObject() returned null.");
  16:                   }
  17:                 }
  18:             }
  19:             if (null != _factory && null != obj) {
  20:                 try {
  21:                     _factory.activateObject(obj);
  22:                     if (!_factory.validateObject(obj)) {
  23:                         throw new Exception("ValidateObject failed");
  24:                     }
  25:                 } catch (Throwable t) {
  26:                     try {
  27:                         _factory.destroyObject(obj);
  28:                     } catch (Throwable t2) {
  29:                         // swallowed
  30:                     } finally {
  31:                         obj = null;
  32:                     } 
  33:                     if (newlyCreated) {
  34:                         throw new NoSuchElementException(
  35:                             "Could not create a validated object, cause: " +
  36:                             t.getMessage());
  37:                     }
  38:                 }
  39:             }
  40:         }
  41:         _numActive++;
  42:         return obj;
  43:     }

在不考虑同步的时候,我们只看对象的创建,典型的几个if逻辑就完成了这样的功能,在pool空时,就用factory的makeObject方法,不空则直接从内置stack里pop一个对象出来。接着在对象构造OK后,开始激活这个对象。接着调用validate去校验,如果不合格就跑出异常,而异常的捕获中又会尝试去destroy对象,再有问题的话只有swallow掉异常了。整个pool会有一个_numActive变量来控制活着(被borrow但没被return)的对象数,每次borrow后都会加一。而循环是到obj不空时才退出的,所以obj不会为null。所以,只要你的makeObject合理的不返回null,那么这个逻辑就不会死循环。当然整个过程中似乎都会有多线程安全问题,所以整个方法都加同步。但是这显然不是完美的解决方法,对于复杂的多线程问题,调度还需要程序员自己来控制。

接着就是returnObject了,顾名思义就是把从pool中借出来的object还回去。逻辑代码如下:

   1: public synchronized void returnObject(Object obj) throws Exception {
   2:         boolean success = !isClosed();
   3:         if(null != _factory) {
   4:             if(!_factory.validateObject(obj)) {
   5:                 success = false;
   6:             } else {
   7:                 try {
   8:                     _factory.passivateObject(obj);
   9:                 } catch(Exception e) {
  10:                     success = false;
  11:                 }
  12:             }
  13:         }
  14:  
  15:         boolean shouldDestroy = !success;
  16:  
  17:         _numActive--;
  18:         if (success) {
  19:             Object toBeDestroyed = null;
  20:             if(_pool.size() >= _maxSleeping) {
  21:                 shouldDestroy = true;
  22:                 toBeDestroyed = _pool.remove(0); // remove the stalest object
  23:             }
  24:             _pool.push(obj);
  25:             obj = toBeDestroyed; // swap returned obj with the stalest one so it can be destroyed
  26:         }
  27:         notifyAll(); // _numActive has changed
  28:  
  29:         if(shouldDestroy) { // by constructor, shouldDestroy is false when _factory is null
  30:             try {
  31:                 _factory.destroyObject(obj);
  32:             } catch(Exception e) {
  33:                 // ignored
  34:             }
  35:         }
  36:     }

首先一个success变量作为判断位来决定对象能否放回pool。如果经过validate后发现对象不合格,那么显然不能放回pool了。如果validate合格,那么就尝试用factory的passivateObject方法使对象变为not active。接着进入逻辑判断如果可以放回,那么尝试将对象push到pool的内置stack中,尝试时会有阈值限定,如果超过了阈值的话,那么需要以某种策略将stack中的对象remove掉一个。而策略如果仔细看代码就会发现很合理,首先在stack中remove最老的一个,然后将这个remove值保存为临时变量,将待放回对象push到stack,接着将临时对象转接到带放回对象上。我们看到,这样的一组行为保证了几个对象的销毁,而不会出现内存泄露。

最后附上apache官网上的几张时序图来完美解释borrow和return的过程:

clip_image002[4]

clip_image002[6]

当出借少于归还

Java并未提供一种机制来保证两个方法被调用的次数之间呈现一种特定的关系(相等,相差一个常数,或是其它任何关系)。因此,完全可以做到建立一个ObjectPool对象,然后调用一次borrowObject方法,借出一个对象,之后重复两次returnObject方法调用,进行两次归还。而调用一个从不曾借出对象的ObjectPool的returnObject方法也并不是一个不可完成的任务。

尽管这些使用方法并不合乎returnObject的字面意思,但是Pool组件中的各个ObjectPool/KeyedObjectPool实现都不在乎这一点。它们的returnObject方法都只是单纯地召唤与当前对象池关联的PoolableObjectFactory实例,看这对象能否经受得起validateObject的考验而已。考验的结果决定了这个对象是应该拿去作passivateObject处理,而后留待重用;还是应该拿去作destroyObject处理,以免占用资源。也就是说,当出借少于归还的时候,并不会额外发生什么特别的事情(当然,有可能因为该对象池处于不接受归还对象的请求的状态而抛出异常,不过这是常规现象)。

在实际使用中,可以利用这一特性来向对象池内加入通过其它方法生成的对象。


线程安全问题

有时候可能要在多线程环境下使用Pool组件,这时候就会遇到和Pool组件的线程安全程度有关的问题。

因为ObjectPool和KeyedObjectPool都是在org.apache.commons.pool中定义的接口,而在接口中无法使用“synchronized”来修饰方法,所以,一个ObjectPool/KeyedObjectPool下的各个方法是否是同步方法,完全要看具体的实现。而且,单纯地使用了同步方法,也并不能使对象就此在多线程环境里高枕无忧。

就Pool组件中自带的几个ObjectPool/KeyedObjectPool的实现而言,它们都在一定程度上考虑了在多线程环境中使用的情况。不过还不能说它们是完全“线程安全”的。

例如,这段代码有些时候就会有一些奇怪的表现,最后输出的结果比预期的要大:

import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.StackObjectPool;
class UnsafePicker extends Thread {
    private ObjectPool pool;
    public UnsafePicker(ObjectPool op) {
        pool = op;
    }
    public void run() {
        Object obj = null;
        try {
        /* 似乎…… */
            if ( pool.getNumActive() < 5 ) {
                sleep((long) (Math.random() * 10));
                obj = pool.borrowObject();
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class UnsafeMultiThreadPoolingSample {
    public static void main(String[] args) {
        ObjectPool pool = new StackObjectPool
                (new BasePoolableObjectFactorySample());
        Thread ts[] = new Thread[20];
        for (int j = 0; j < ts.length; j++) {
            ts[j] =  new UnsafePicker(pool);
            ts[j].start();
        }
        try {
            Thread.sleep(1000);
            /* 然而…… */
            System.out.println("NumActive:" + pool.getNumActive());
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

要避免这种情况,就要进一步采取一些措施才行:

import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.StackObjectPool;
class SafePicker extends Thread {
    private ObjectPool pool;
    public SafePicker(ObjectPool op) {
        pool = op;
    }
    public void run() {
        Object obj = null;
        try {
            /* 略加处理 */
            synchronized (pool) {
                if ( pool.getNumActive() < 5 ) {
                    sleep((long) (Math.random() * 10));
                    obj = pool.borrowObject();
                }
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class SafeMultiThreadPoolingSample {
    public static void main(String[] args) {
        ObjectPool pool = new StackObjectPool
                (new BasePoolableObjectFactorySample());
        Thread ts[] = new Thread[20];
        for (int j = 0; j < ts.length; j++) {
            ts[j] =  new SafePicker(pool);
            ts[j].start();
        }
        try {
            Thread.sleep(1000);
            System.out.println("NumActive:" + pool.getNumActive());
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

基本上,可以说Pool组件是线程相容的。但是要在多线程环境中使用,还需要作一些特别的处理。

什么时候不要池化

采用对象池化的本意,是要通过减少对象生成的次数,减少花在对象初始化上面的开销,从而提高整体的性能。然而池化处理本身也要付出代价,因此,并非任何情况下都适合采用对象池化。

Dr. Cliff Click在JavaOne 2003上发表的《Performance Myths Exposed》中,给出了一组其它条件都相同时,使用与不使用对象池化技术的实际性能的比较结果。他的实测结果表明:

  • 对于类似Point这样的轻量级对象,进行池化处理后,性能反而下降,因此不宜池化;
  • 对于类似Hashtable这样的中量级对象,进行池化处理后,性能基本不变,一般不必池化(池化会使代码变复杂,增大维护的难度);
  • 对于类似JPanel这样的重量级对象,进行池化处理后,性能有所上升,可以考虑池化。

根据使用方法的不同,实际的情况可能与这一测量结果略有出入。在配置较高的机器和技术较强的虚拟机上,不宜池化的对象的范围可能会更大。不过,对于像网络和数据库连接这类重量级的对象来说,目前还是有池化的必要。

基本上,只在重复生成某种对象的操作成为影响性能的关键因素的时候,才适合进行对象池化。如果进行池化所能带来的性能提高并不重要的话,还是不采用对象池化技术,以保持代码的简明,而使用更好的硬件和更棒的虚拟机来提高性能为佳。

posted @ 2011-06-02 16:14  跳刀的兔子  阅读(6625)  评论(0编辑  收藏  举报