apache-common-pool2對象池的使用

一、概述

大多時候,我們獲取對象的方法都是直接new一個。但是,對于大對象的構造,或者構造耗時比較久的對象,我們每次要使用都去new一個是很不科學的。比如數(shù)據(jù)庫的連接對象、redis的連接對象、Http連接請求對象等等。在設計模式中有一個專門的模式來解決這種場景下的問題,即享元模式。

享元模式其實很好理解,也就是構造一個對象池,這個對象池中維護一定數(shù)量的對象,需要的時候就從這個對象池中獲取對象,使用完后返還給對象池。這樣就避免構造對象所帶來的耗時,提升了系統(tǒng)的性能。

設計這樣的一個對象池看起來好像并不難,甚至覺的只需要一個List就可以做到。但是,如果考慮到系統(tǒng)的伸縮性,比如在系統(tǒng)忙時可能需要對象池中有足夠的對象可以被拿來使用,以保證系統(tǒng)大多時候應該等待對象而進入阻塞,同時,在系統(tǒng)閑時又不需要太多的對象存放在對象池中,這時候就需要釋放一些對象。另外,還需要考慮對象何時構造,何時銷毀,對象異常的處理等問題。

為了讓大多java程序員不重復造輪子,apache開發(fā)了一個庫,專門給需要對象池的程序提供一個底層的支持。這個庫也就是apche-common-pool2,使用這個庫,我們只需要關注對象的生成、銷毀、校驗等操作就可以了。對象池的具體實現(xiàn)細節(jié)都交給common-pool2中的具體對象池實現(xiàn)類來完成。

二、maven地址

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.4.2</version>
</dependency>

三、相關接口

common-pool2下有幾個很重要的接口,common-pool2也是以這幾個接口為基礎進行開發(fā)的。
ObjectPool.java,抽象了對象池的一個整體的行為。

    //從對象池中獲取對象的方法
    T borrowObject() throws Exception, NoSuchElementException,
            IllegalStateException;
    //將對象返還給對象池
    void returnObject(T obj) throws Exception;
    //讓對象失效
    void invalidateObject(T obj) throws Exception;
    //往對象池中新增一個對象
    void addObject() throws Exception, IllegalStateException,
            UnsupportedOperationException;
    //獲取當前閑置在對象池中的對象數(shù)量,即沒有被拿走使用的對象數(shù)量
    int getNumIdle();
    //獲取已經(jīng)在使用中的對象數(shù)量,即被使用者從對象池中拿走使用的數(shù)量
    int getNumActive();
    //清空對象池中閑置的所有對象
    void clear() throws Exception, UnsupportedOperationException;
    //關閉對象池
    void close();

PooledObject,抽象了對象池中對象應該具備的一些屬性。注意,這個對象并不是我們真正要存的對象,而是經(jīng)過一層封裝的對象。比如我們想往對象池存放String類型的對象,那么真正存放在對象池的其實都是經(jīng)過封裝過的對象,即PooledObject<String>對象。

public interface PooledObject<T> extends Comparable<PooledObject<T>> {
    T getObject();
    long getCreateTime();
    long getActiveTimeMillis();
    long getIdleTimeMillis();
    long getLastBorrowTime();
    long getLastReturnTime();
    long getLastUsedTime();
    @Override
    int compareTo(PooledObject<T> other);
    @Override
    boolean equals(Object obj);
    @Override
    int hashCode();
    String toString();
    boolean startEvictionTest();
    boolean endEvictionTest(Deque<PooledObject<T>> idleQueue);
    boolean allocate();
    boolean deallocate();
    void invalidate();
    void setLogAbandoned(boolean logAbandoned);
    void use();
    void printStackTrace(PrintWriter writer);
    PooledObjectState getState();
    void markAbandoned();
    void markReturning();
}

PooledObjectFactory.java,抽象了生成對象的工廠模型

public interface PooledObjectFactory<T> {
  //構造一個封裝對象
  PooledObject<T> makeObject() throws Exception;
  //銷毀對象
  void destroyObject(PooledObject<T> p) throws Exception;
  //驗證對象是否可用
  boolean validateObject(PooledObject<T> p);
  //激活一個對象,使其可用用
  void activateObject(PooledObject<T> p) throws Exception;
   //鈍化一個對象,也可以理解為反初始化
  void passivateObject(PooledObject<T> p) throws Exception;
}

同時還有KeyedObjectPool,KeyedPooledObjectFactory接口,主要提供了帶有鍵的對象池模板。帶有鍵的對象池可以理解為一個 Map<key,ObjectPool> 。也就是每個鍵都對應一個對象池。

四、常規(guī)對象池的使用

下面舉個例子,假設我們要做一個維護數(shù)據(jù)庫連接的對象池。我們只需要定義兩個類
DbConnection,用于連接數(shù)據(jù)庫的對象。為了讓代碼不那么復雜,下面的類并沒有連接數(shù)據(jù)庫的代碼,只有一個簡單的屬性,表示是否連接上數(shù)據(jù)庫。

public class DbConnection {

    private Boolean isActive;

    public Boolean getActive() {
        return isActive;
    }

    public void setActive(Boolean active) {
        isActive = active;
    }
}

再定義一個構造DbConnection的工廠類DbConnectionFactory,然后實現(xiàn)一些方法

public class DbConnectionFactory implements PooledObjectFactory<DbConnection> {

    @Override
    public PooledObject<DbConnection> makeObject() throws Exception {
        DbConnection dbConnection = new DbConnection();
        //構造一個新的連接對象
        return new DefaultPooledObject<>(dbConnection);
    }

    @Override
    public void destroyObject(PooledObject<DbConnection> p) throws Exception {
        //斷開連接
        p.getObject().setActive(false);
    }

    @Override
    public boolean validateObject(PooledObject<DbConnection> p) {
        //判斷這個對象是否是保持連接狀態(tài)
        return p.getObject().getActive();
    }

    @Override
    public void activateObject(PooledObject<DbConnection> p) throws Exception {
        //激活這個對象,讓它連接上數(shù)據(jù)庫
        p.getObject().setActive(true);
    }

    @Override
    public void passivateObject(PooledObject<DbConnection> p) throws Exception {
        //不處理
    }
}

到這里,我們就可以使用common-pool2自帶的GenericObjectPool類了。

public static void main(String[] args) {
        DbConnectionFactory factory = new DbConnectionFactory();
        //設置對象池的相關參數(shù)
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        poolConfig.setMaxIdle(20);
        poolConfig.setMaxTotal(100);
        poolConfig.setMinIdle(5);
        //新建一個對象池,傳入對象工廠和配置
        GenericObjectPool<DbConnection> objectPool = new GenericObjectPool<>(factory, poolConfig);
        DbConnection dbConnection = null;
        try {
            //從對象池獲取對象,如果
            dbConnection = objectPool.borrowObject();
            System.out.println(dbConnection.getActive());
            //使用改對象
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (dbConnection != null) {
                //返還對象
                objectPool.returnObject(dbConnection);
            }
        }
    }

這樣,我們就已經(jīng)完成的常規(guī)對象池的所有開發(fā)。

SoftReferenceObjectPool

common-pool2還提供了一個軟引用對象池的實現(xiàn)類,原理和GenericObjectPool差不多,不過對象池中存放的封裝對象是軟引用對象。軟引用對象主要是在堆空間不足會被GC回收,再具體的細節(jié)請讀者自行百度。

五、帶有鍵的對象池

目前我們看到的對象池的對象都是屬于一個類型的,也就是說如果要求對象池中存放的對象類型不一樣,常規(guī)對象池就做不到了。
比如現(xiàn)在有這樣一個場景,項目中要連接的數(shù)據(jù)庫比較多,每個數(shù)據(jù)庫的地址賬號密碼都不一樣,但是上面的常規(guī)對象池存放的連接對象好像沒辦法在取對象時指定獲取哪個數(shù)據(jù)庫的連接對象。這是,就需要帶有鍵的對象池,也就是GenericKeyedObjectPool。

我們再定義個DbConnection2,多一個字段url,來表示屬于哪個數(shù)據(jù)庫的連接

public class DbConnection2 {

    private Boolean isActive;
    private String url;

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public Boolean getActive() {
        return isActive;
    }

    public void setActive(Boolean active) {
        isActive = active;
    }
}

再實現(xiàn)一個KeyedPooledObjectFactory類

public class DbConnectionKeyFactory implements KeyedPooledObjectFactory<String, DbConnection2> {

    @Override
    public PooledObject<DbConnection2> makeObject(String key) throws Exception {
        DbConnection2 dbConnection2 = new DbConnection2();
        dbConnection2.setUrl(key);
        dbConnection2.setActive(true);
        return new DefaultPooledObject<>(dbConnection2);
    }

    @Override
    public void destroyObject(String key, PooledObject<DbConnection2> p) throws Exception {
        p.getObject().setActive(false);
    }

    @Override
    public boolean validateObject(String key, PooledObject<DbConnection2> p) {
        return p.getObject().getActive();
    }

    @Override
    public void activateObject(String key, PooledObject<DbConnection2> p) throws Exception {
        p.getObject().setActive(true);
    }

    @Override
    public void passivateObject(String key, PooledObject<DbConnection2> p) throws Exception {

    }
}

之后就可以使用GenericKeyedObjectPool對象池了

public static void main(String[] args) {
        GenericKeyedObjectPoolConfig genericKeyedObjectPoolConfig = new GenericKeyedObjectPoolConfig();
        genericKeyedObjectPoolConfig.setMaxIdlePerKey(10);
        genericKeyedObjectPoolConfig.setMaxTotalPerKey(100);
        genericKeyedObjectPoolConfig.setMaxTotal(500);
        genericKeyedObjectPoolConfig.setMinIdlePerKey(10);

        DbConnectionKeyFactory dbConnectionKeyFactory = new DbConnectionKeyFactory();
        GenericKeyedObjectPool<String, DbConnection2> genericKeyedObjectPool = new GenericKeyedObjectPool<>
                (dbConnectionKeyFactory, genericKeyedObjectPoolConfig);
        DbConnection2 dbConnection1 = null;
        DbConnection2 dbConnection2 = null;
        try {
            dbConnection1 = genericKeyedObjectPool.borrowObject("192.168.0.1");
            dbConnection2 = genericKeyedObjectPool.borrowObject("192.168.0.2");
            System.out.println(dbConnection1.getUrl());
            System.out.println(dbConnection2.getUrl());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (dbConnection1 != null) {
                genericKeyedObjectPool.returnObject(dbConnection1.getUrl(), dbConnection1);
            }
            if (dbConnection2 != null) {
                genericKeyedObjectPool.returnObject(dbConnection2.getUrl(), dbConnection2);
            }
        }
    }

六、存放代理對象的對象池

如果需要在對象池中存放代理對象,common-pool2也提供了兩個現(xiàn)有的對象池實現(xiàn)類ProxiedObjectPoolProxiedKeyedObjectPool,我們下面簡單介紹下ProxiedObjectPool的使用。
我們要用到jdk的對象代理機制,所以需要定義一個代表連接對象的接口類DbConnectionInterface

public interface DbConnectionInterface {
    public Boolean getActive();
    public void setActive(Boolean active);
}

之后定義一個實現(xiàn)類DbConnection

public class DbConnection implements DbConnectionInterface {

    private Boolean isActive;

    public Boolean getActive() {
        return isActive;
    }

    public void setActive(Boolean active) {
        isActive = active;
    }
}

為了方便觀察代理的效果,我們需要再定義一下封裝對象MyDefaultPooledObject,直接繼承DefaultPooledObject類就好了。重寫一下use()方法

public class MyDefaultPooledObject extends DefaultPooledObject<DbConnectionInterface> {
    public MyDefaultPooledObject(DbConnectionInterface object) {
        super(object);
    }

    @Override
    //代理對象被調(diào)用時,會回調(diào)這個方法
    public void use() {
        super.use();
        DbConnectionInterface object = getObject();
        System.out.println("method get " + object.getActive());
    }
}

最后還要定義一個生產(chǎn)DbConnectionInterface對象的工廠類DbConnectionFactory

public class DbConnectionFactory implements PooledObjectFactory<DbConnectionInterface> {

    @Override
    public PooledObject<DbConnectionInterface> makeObject() throws Exception {
        DbConnectionInterface dbConnection = new DbConnection();
        return new MyDefaultPooledObject(dbConnection);
    }

    @Override
    public void destroyObject(PooledObject<DbConnectionInterface> p) throws Exception {
        p.getObject().setActive(false);
    }

    @Override
    public boolean validateObject(PooledObject<DbConnectionInterface> p) {
        return p.getObject().getActive();
    }

    @Override
    public void activateObject(PooledObject<DbConnectionInterface> p) throws Exception {
        p.getObject().setActive(true);
    }

    @Override
    public void passivateObject(PooledObject<DbConnectionInterface> p) throws Exception {
        //不處理
    }
}

然后就可以開始使用ProxiedObjectPool對象池了

public static void main(String[] args) {
        DbConnectionFactory factory = new DbConnectionFactory();
        //這里必須定義一個AbandonedConfig配置,并且設置useUsageTracking屬性為true
        //這樣代理對象的方法被調(diào)用時,才會去調(diào)用DefaultPooledObject的use()方法
        AbandonedConfig abandonedConfig = new AbandonedConfig();
        abandonedConfig.setUseUsageTracking(true);
        //定義一個常規(guī)對象池
        GenericObjectPool<DbConnectionInterface> pool = new GenericObjectPool<>(factory, new GenericObjectPoolConfig()
                , abandonedConfig);
        pool.setMaxTotal(100); // 對象池中允許的最大對象數(shù)量
        pool.setMaxIdle(50);
        pool.setMaxWaitMillis(5000);
        pool.setTimeBetweenEvictionRunsMillis(10000);
        pool.setMinIdle(20);
        // 使用jdk自帶的代理機制
        JdkProxySource<DbConnectionInterface> proxySource = new JdkProxySource<>(DbConnectionInterface.class
                .getClassLoader(), new
                Class[]{DbConnectionInterface.class});
        // 加裝代理新對象池
        ProxiedObjectPool<DbConnectionInterface> proxiedPool = new ProxiedObjectPool<>(pool, proxySource);
        DbConnectionInterface connection = null;
        try {
            connection = proxiedPool.borrowObject();
            //每次代理對象被調(diào)用時,都會執(zhí)行MyDefaultPooledObject里的use方法
            connection.getActive();
            //這里輸出class com.sun.proxy.$Proxy0,說明這是一個代理對象
            System.out.println(connection.getClass());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                try {
                    proxiedPool.returnObject(connection);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        }
    }

這樣,通過代理對象池取出的所有對象在調(diào)用方法時,都會經(jīng)過我們定義的MyDefaultPooledObject#use()方法。

其實代理對象池的原理很簡單,就是通過jdk的代理機制為剛創(chuàng)建的封裝對象再創(chuàng)建一個代理對象,這樣放到對象池中的就是代理對象了。當代理對象的任一方法被調(diào)用時,都會觸發(fā)JdkProxyHandler#invoke()執(zhí)行,也就是下面這個方法

@Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        return doInvoke(method, args);
    }

Object doInvoke(Method method, Object[] args) throws Throwable {
        validateProxiedObject();
        T object = getPooledObject();
        if (usageTracking != null) {
          //這里的usageTracking其實就是我們的常規(guī)對象池GenericObjectPool
          //GenericObjectPool實現(xiàn)了UsageTracking接口
            usageTracking.use(object);
        }
        return method.invoke(object, args);
    }

所以每個代理對象的方法調(diào)用都會觸發(fā)GenericObjectPool#use()方法,use()方法里面會判斷useUsageTracking是否開啟來決定是否調(diào)用poolObject#use()方法。

@Override
    public void use(T pooledObject) {
        AbandonedConfig ac = this.abandonedConfig;
        if (ac != null && ac.getUseUsageTracking()) {
            PooledObject<T> wrapper = allObjects.get(new IdentityWrapper<T>(pooledObject));
            wrapper.use();
        }
    }

ProxiedKeyedObjectPool的原理也差不多,就不多做介紹了。

七、總結

總的來說,common-pool2給我們提供了5種不同類型的對象池實現(xiàn)來給我們使用:

  1. GenericObjectPool
  2. SoftReferenceObjectPool
  3. GenericKeyedObjectPool
  4. ProxiedObjectPool
  5. ProxiedKeyedObjectPool

一般情況下,GenericObjectPool足夠滿足我們大部分的場景,而且用起來也很簡單,我們只需要實現(xiàn)一個對象工廠類就可以了,可謂是開箱即用。另外SoftReferenceObjectPoolGenericKeyedObjectPool也有不少的使用場景,也很好用。

對于ProxiedObjectPool和ProxiedKeyedObjectPool,個人感覺并不太好用。因為代理對象只會在方法被調(diào)用前執(zhí)行一些自定義方法,而沒有在方法被調(diào)用后執(zhí)行一些方法。另外,要開啟代理對象池還需要專門設置AbandonedConfig,很是麻煩??赡苓€不如自己基于GenericObjectPool實現(xiàn)一個。當然,也可能是我理解不夠,如果有不同意見的讀者歡迎指出。

我的CSDN博客地址
https://blog.csdn.net/u013332124/article/details/81042375

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容