程序員的福音 - Apache Commons Pool

此文是系列文章第十三篇,前幾篇請點(diǎn)擊鏈接查看

程序猿的福音 - Apache Commons簡介

程序員的福音 - Apache Commons Lang

程序員的福音 - Apache Commons IO

程序員的福音 - Apache Commons Codec

程序員的福音 - Apache Commons Compress

程序員的福音 - Apache Commons Exec

程序員的福音 - Apache Commons Email

程序員的福音 - Apache Commons Net

程序員的福音 - Apache Commons Collections

程序員的福音 - Apache Commons HttpClient

程序員的福音 - Apache Commons VFS(上)

程序員的福音 - Apache Commons VFS(下)

Apache Commons Pool 開源軟件庫提供了一個(gè)對象池 API 和許多對象池實(shí)現(xiàn)。

Pool 有兩個(gè)版本,目前主流的是 Pool2疾瓮,與 1.x 系列相比尘颓,Apache Commons Pool2 重新編寫了對象池實(shí)現(xiàn)佃声。除了性能和可伸縮性改進(jìn)之外菲嘴,版本2還包括健壯的實(shí)例跟蹤和對象池監(jiān)控虏辫。

后續(xù)文章出現(xiàn)的 Commons-Pool 指的就是 Pool2蚌吸。

Commons-Net目前最新版本是2.9.0,最低要求Java8以上砌庄。

maven坐標(biāo)如下:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.9.0</version>
</dependency>

包結(jié)構(gòu)如下:

org.apache.commons.pool2org.apache.commons.pool2.implorg.apache.commons.pool2.proxy

下面簡單介紹一下其用法羹唠。

01. 簡介

為什么要有對象池呢,假如一個(gè)對象創(chuàng)建耗時(shí) 500 毫秒娄昆,而我們調(diào)用它的方法僅耗時(shí) 10 毫秒佩微,這種情況每次使用都 new 的話性價(jià)比很低,相當(dāng)于每次都要耗費(fèi) 550 毫秒萌焰。 對象池就是為了解決此類問題而誕生的哺眯,對于這些昂貴的對象來說,提前創(chuàng)建若干個(gè)對象用對象池管理起來扒俯,用的時(shí)候從對象池借來一個(gè)奶卓,用完后歸還 可以大大提升性能。

對象池是一種享元模式的實(shí)現(xiàn)撼玄,常用于各種連接池的實(shí)現(xiàn)夺姑。 比如我們常見的數(shù)據(jù)庫連接池 DBCP,Redis 客戶端 Jedis 等都依賴 Commons-Pool掌猛。

Commons-Pool 主要有三個(gè)角色:

PooledObject池化對象盏浙,用于包裝實(shí)際的對象,提供一些附件的功能留潦。如 Commons-Pool 自帶的 DefaultPooledObject 會記錄對象的創(chuàng)建時(shí)間只盹,借用時(shí)間,歸還時(shí)間兔院,對象狀態(tài)等殖卑,PooledSoftReference 使用 Java 的軟引用來持有對象,便于 JVM 內(nèi)存不夠時(shí)回收對象坊萝。當(dāng)然我們也可以實(shí)現(xiàn) PooledObject 接口來定義我們自己的對象包裝器孵稽。

PooledObjectFactory對象工廠,ObjectPool 對于每個(gè)對象的核心操作會代理給 PooledObjectFactory十偶。

需要一個(gè)新實(shí)例時(shí)菩鲜,就調(diào)用 makeObject 方法。

需要借用對象時(shí)會調(diào)用 activateObject 方法激活對象惦积,并且根據(jù)配置情況決定是否驗(yàn)證對象有效性接校,通過 validateObject 方法驗(yàn)證。

歸還對象時(shí)會調(diào)用 passivateObject 方法鈍化對象。

需要銷毀對象時(shí)候調(diào)用 destroyObject 方法蛛勉。

PooledObjectFactory 必須是線程安全的鹿寻。

ObjectPool對象池接口,用于管理池中的所有對象诽凌,對于每個(gè)對象的操作會代理給 ObjectFactory毡熏。ObjectPool 有多個(gè)實(shí)現(xiàn),GenericObjectPool 提供了多種配置選項(xiàng)侣诵,包括限制空閑或活動(dòng)實(shí)例的數(shù)量痢法、在實(shí)例處于池中空閑時(shí)將其逐出等。從版本 2 開始杜顺,GenericObjectPool 還提供了廢棄實(shí)例跟蹤和刪除功能财搁。SoftReferenceObjectPool 可以根據(jù)需要增長,但允許垃圾收集器根據(jù)需要從池中逐出空閑實(shí)例躬络。

以下是部分類圖

圖片
圖片

02. 使用方式

Commons-Pool 用起來很簡單妇拯,下面我用一個(gè)例子簡單介紹下其用法

首先我們創(chuàng)建一個(gè)對象用于測試,對象構(gòu)造函數(shù)使用隨機(jī)延遲模擬創(chuàng)建的復(fù)雜

/**
 * 復(fù)雜的對象洗鸵,創(chuàng)建出來比較耗時(shí)間
 */
public class ComplexObject {

    private String name;

    public ComplexObject(String name) {
        try {
            long t1 = System.currentTimeMillis();
            // 模擬創(chuàng)建耗時(shí)操作
            ThreadLocalRandom tlr = ThreadLocalRandom.current();
            Thread.sleep(4000 + tlr.nextInt(2000));
            long t2 = System.currentTimeMillis();
            System.out.println(name + " 創(chuàng)建耗時(shí): " + (t2-t1) + "ms");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

其次創(chuàng)建一個(gè) ComplexPooledObjectFactory 實(shí)現(xiàn)。當(dāng)我們有復(fù)雜的操作仗嗦,比如激活對象膘滨,鈍化對象,銷毀對象(需要釋放資源)等就需要實(shí)現(xiàn) PooledObjectFactory 來定制稀拐,如果沒有這些操作選擇繼承 BasePooledObjectFactory 抽象類更方便火邓。下面分別給出兩種創(chuàng)建的代碼示例

1. 繼承 BasePooledObjectFactory

public class SimplePooledObjectFactory extends BasePooledObjectFactory<ComplexObject> {
    @Override
    public ComplexObject create() {
        // 隨機(jī)指定一個(gè)名稱,用于區(qū)分ComplexObject
        String name = "test" + ThreadLocalRandom.current().nextInt(100);
        return new ComplexObject(name);
    }
    @Override
    public PooledObject<ComplexObject> wrap(ComplexObject obj) {
        // 使用默認(rèn)池化對象包裝ComplexObject
        return new DefaultPooledObject(obj);
    }
}

2. 實(shí)現(xiàn) PooledObjectFactory

public class ComplexPooledObjectFactory implements PooledObjectFactory<ComplexObject> {

    @Override
    public PooledObject<ComplexObject> makeObject() {
        // 隨機(jī)指定一個(gè)名稱德撬,用于區(qū)分ComplexObject并使用默認(rèn)池化對象包裝ComplexObject
        String name = "test" + ThreadLocalRandom.current().nextInt(100);
        return new DefaultPooledObject<>(new ComplexObject(name));
    }

    @Override
    public void destroyObject(PooledObject<ComplexObject> p) {
        // 銷毀對象铲咨,當(dāng)清空,空閑對象大于配置值等會銷毀多余對象
        // 此處應(yīng)釋放掉對象占用的資源蜓洪,如關(guān)閉連接纤勒,關(guān)閉IO等
    }

    @Override
    public boolean validateObject(PooledObject<ComplexObject> p) {
        // 驗(yàn)證對象狀態(tài)是否正常,是否可用
        return true;
    }

    @Override
    public void activateObject(PooledObject<ComplexObject> p) {
        // 激活對象隆檀,使其可用
    }

    @Override
    public void passivateObject(PooledObject<ComplexObject> p) {
        // 鈍化對象摇天,使其不可用
    }
}

最后編寫測試代碼

public static void main(String[] args) throws Exception {
    // 創(chuàng)建配置對象
    GenericObjectPoolConfig<ComplexObject> poolConfig = new GenericObjectPoolConfig<>();
    // 最大空閑實(shí)例數(shù),空閑超過此值將會被銷毀淘汰
    poolConfig.setMaxIdle(5);
    // 最大對象數(shù)量恐仑,包含借出去的和空閑的
    poolConfig.setMaxTotal(20);
    // 最小空閑實(shí)例數(shù)泉坐,對象池將至少保留2個(gè)空閑對象
    poolConfig.setMinIdle(2);
    // 對象池滿了,是否阻塞獲壬哑汀(false則借不到直接拋異常)
    poolConfig.setBlockWhenExhausted(true);
    // BlockWhenExhausted為true時(shí)生效腕让,對象池滿了阻塞獲取超時(shí),不設(shè)置則阻塞獲取不超時(shí)歧斟,也可在borrowObject方法傳遞第二個(gè)參數(shù)指定本次的超時(shí)時(shí)間
    poolConfig.setMaxWaitMillis(3000);
    // 創(chuàng)建對象后是否驗(yàn)證對象纯丸,調(diào)用objectFactory#validateObject
    poolConfig.setTestOnCreate(false);
    // 借用對象后是否驗(yàn)證對象 validateObject
    poolConfig.setTestOnBorrow(true);
    // 歸還對象后是否驗(yàn)證對象 validateObject
    poolConfig.setTestOnReturn(true);
    // 每30秒定時(shí)檢查淘汰多余的對象, 啟用單獨(dú)的線程處理
    poolConfig.setTimeBetweenEvictionRunsMillis(1000 * 60 * 30);
    // 每30秒定時(shí)檢查期間是否驗(yàn)證對象 validateObject
    poolConfig.setTestWhileIdle(false);
    // jmx監(jiān)控偏形,和springboot自帶的jmx沖突,可以選擇關(guān)閉此配置或關(guān)閉springboot的jmx配置
    poolConfig.setJmxEnabled(false);

    ComplexPooledObjectFactory objectFactory = new ComplexPooledObjectFactory();
    GenericObjectPool<ComplexObject> objectPool = new GenericObjectPool<>(objectFactory, poolConfig);
    // 申請對象
    ComplexObject obj1 = objectPool.borrowObject();
    println("第一次申請對象:" + obj1.getName());
    // returnObject應(yīng)該放在finally中 避免業(yè)務(wù)異常沒有歸還對象液南,demo僅做示例
    objectPool.returnObject(obj1);
    // 申請對象壳猜, 由于之前歸還了,借用的還是之前的對象
    ComplexObject obj2 = objectPool.borrowObject();
    println("第二次申請對象:" + obj2.getName());
    // 再次申請對象滑凉,由于之前沒有歸還统扳,借用的是新創(chuàng)建的
    ComplexObject obj3 = objectPool.borrowObject();
    println("第三次申請對象:" + obj3.getName());

    // returnObject應(yīng)該放在finally中 避免業(yè)務(wù)異常沒有歸還對象,demo僅做示例
    objectPool.returnObject(obj2);
    objectPool.returnObject(obj3);
}

運(yùn)行結(jié)果如下

test41 創(chuàng)建耗時(shí): 5400ms
第一次申請對象:test41
第二次申請對象:test41
test58 創(chuàng)建耗時(shí): 5349ms
第三次申請對象:test58

當(dāng)然如果借用次數(shù)越多畅姊,節(jié)省下來的時(shí)間就越多咒钟。

由于示例比較簡單粗暴,在對象池剛剛創(chuàng)建還沒提前創(chuàng)建好對象若未,我們就去使用了朱嘴,所以效果不是很理想,正常使用效果會比較好粗合。

03. KeyedObjectPool

Commons-Pool 還有 KeyedPooledObjectFactory萍嬉,KeyedObjectPool 接口,它支持 Key Value 形式隙疚。

public interface KeyedPooledObjectFactory<K, V> {
    // 通過參數(shù)創(chuàng)建對象
    PooledObject<V> makeObject(K key);
    // 通過參數(shù)激活對象壤追,使其可用
    void activateObject(K key, PooledObject<V> obj);
    // 通過參數(shù)鈍化對象,使其不可用
    void passivateObject(K key, PooledObject<V> obj);
    // 通過參數(shù)驗(yàn)證對象狀態(tài)是否正常供屉,是否可用
    boolean validateObject(K key, PooledObject<V> obj);
    // 通過參數(shù)銷毀對象行冰,當(dāng)清空,空閑對象大于配置值等會銷毀多余對象
    // 此處應(yīng)釋放掉對象占用的資源伶丐,如關(guān)閉連接悼做,關(guān)閉IO等
    void destroyObject(K key, PooledObject<V> obj);
}

具體使用方式就不做介紹了,用法和第二節(jié)的類似哗魂,區(qū)別是對象借用和歸還操作需要額外傳遞自定義的 Key 參數(shù)肛走。

04. 總結(jié)

Commons-Pool 作為對象池工具包,支持對象的管理啡彬、跟蹤和監(jiān)控羹与,并且支持自定義池化對象來擴(kuò)展對象管理的行為,如果有相關(guān)需求可以使用庶灿。

后續(xù)章節(jié)我將繼續(xù)給大家介紹 commons 中其他好用的工具類庫纵搁,期待你的關(guān)注。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末往踢,一起剝皮案震驚了整個(gè)濱河市腾誉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖利职,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件趣效,死亡現(xiàn)場離奇詭異,居然都是意外死亡猪贪,警方通過查閱死者的電腦和手機(jī)跷敬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來热押,“玉大人西傀,你說我怎么就攤上這事⊥把ⅲ” “怎么了拥褂?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長牙寞。 經(jīng)常有香客問我饺鹃,道長,這世上最難降的妖魔是什么间雀? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任悔详,我火速辦了婚禮,結(jié)果婚禮上惹挟,老公的妹妹穿的比我還像新娘伟端。我一直安慰自己,他們只是感情好匪煌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著党巾,像睡著了一般萎庭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上齿拂,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天驳规,我揣著相機(jī)與錄音,去河邊找鬼署海。 笑死吗购,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的砸狞。 我是一名探鬼主播捻勉,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼刀森!你這毒婦竟也來了踱启?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎埠偿,沒想到半個(gè)月后透罢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡冠蒋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年羽圃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抖剿。...
    茶點(diǎn)故事閱讀 39,977評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡朽寞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出牙躺,到底是詐尸還是另有隱情愁憔,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布孽拷,位于F島的核電站吨掌,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏脓恕。R本人自食惡果不足惜膜宋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望炼幔。 院中可真熱鬧秋茫,春花似錦、人聲如沸乃秀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽跺讯。三九已至枢贿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間刀脏,已是汗流浹背局荚。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留愈污,地道東北人耀态。 一個(gè)月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像暂雹,于是被迫代替她去往敵國和親首装。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容