jedisPool實現原理及源碼分析(1)----對象池的說明


redis的並發競爭問題如何解決?

  Redis為單進程單線程模式,采用隊列模式將並發訪問變為串行訪問。Redis本身沒有鎖的概念,Redis對於多個客戶端連接並不存在競爭,但是在Jedis客戶端對Redis進行並發訪問時會發生連接超時、數據轉換錯誤、阻塞、客戶端關閉連接等問題,這些問題均是由於客戶端連接混亂造成。對此有2種解決方法:

1.客戶端角度,為保證每個客戶端間正常有序與Redis進行通信,對連接進行池化,同時對客戶端讀寫Redis操作采用內部鎖synchronized。
2.服務器角度,利用setnx實現鎖。
對於第一種,需要應用程序自己處理資源的同步,可以使用的方法比較通俗,可以使用synchronized也可以使用lock;

這里我要提到的是jedis的池化,即jedisPool

      對象的池化技術:

   我們都知道一個對象比如car(對象)在其生命周期大致可氛圍"創建","使用","銷毀"三大階段,那么它的總時間就是T1(創建)+T2(使用)+T3(銷毀),如果創建N個對象都需要這樣的步驟的話是非常耗性能的,就算JVM對垃圾回收機制有優化,但"創建"和"銷毀"多少總會占用部分資源,那么我們就會想能否像常量池那樣,讓對象可復用,從而減少T1和T3所消耗的資源呢?這就引出了我們今天的內容-----對象池化技術即ObjectPool  (jedis也是一個Object對象,我們下面先介紹對象池)

  官網對對象池的解釋是:

  將用過的對象保存起來,等下次需要這種對象的時候再拿出來重復使用,從而在一定程度上減少頻繁創建對象所造成的開銷,用於充當保存對象的"容器"對象,被稱為"對象池"。

      對於沒有狀態的對象,String,在重復使用之前,無需進行任何處理,對於有狀態的對象如StringBuffer,在重復使用之前,需要恢復到同等於生成的狀態,如果恢復不了的話,就只能丟掉,改用創建的實例了。並非所有對象都可以用來池化,因為維護池也需要開銷,對生成時開銷不大的對象進行池化,它可能降低性能。

 

在上述的對對象池的描述來看,在看源碼之前我問了自己一個問題,如果讓你來做對象池你覺的應該注意什么?

  我們舉個例子,所謂對象池嘛肯定是管理對象的,我們將對象看成是一台公共自行車,將對象池比作一個自行車站點。那么我們來想象着,我們怎么可以設計對象池

      首先對於這個站點的功能我們猜想,肯定有"借"自行車,"還"自行車,定期"檢查"自行車車況,"銷毀"(壞了的,拿去修或者銷毀)這幾個基本的功能。
      那對於自行車呢?站點肯定不能自己造自行車,肯定需要一個自行車工廠去維護自行車,那么自然我們想想這個工廠會有哪些功能

  首先作為工廠,那么肯定會有"生產"功能,"檢測"功能,"出庫"功能,"銷毀"功能。

 有了上述的假象之后,我們帶着這些概念,看看真正的代碼是怎么設計的,是不是跟我想的有點類似,下面我們就來看源代碼

  PooledObjectFactory、ObjectPool和ObjectPoolFactory 
  在Pool組件中,對象池化的工作被划分給了三類對象: 

  • PooledObjectFactory用於管理被池化的對象的產生、激活、掛起、校驗和銷毀;
  • ObjectPool用於管理要被池化的對象的借出和歸還,並通知PoolableObjectFactory完成相應的工作;
  • ObjectPoolFactory則用於大量生成相同類型和設置的ObjectPool。

  相應地,使用Pool組件的過程,也大體可以划分成“創立PooledObjectFactory”、“使用ObjectPool”和可選的“利用ObjectPoolFactory”三步。

       PooledObjectFactory

   是一個接口,因為工程可以多多種工廠,自行車,客車,卡車等等。該接口jdk沒有默認實現,需要自己實現。點開對象的工廠,我驚奇的發現,完全驗證了我的猜想

  只是在基礎功能的基礎上,多增加了一些其他的功能罷了。

import org.apache.commons.pool.PoolableObjectFactory;

// 1) 創建一個實現了PoolableObjectFactory接口的類。
public class PoolableObjectFactorySample implements PoolableObjectFactory {

    private static int counter = 0;

    // 2) 為這個類添加一個Object makeObject()方法。這個方法用於在必要時產生新的對象。
    public Object makeObject() throws Exception {
        Object obj = String.valueOf(counter++);
        System.err.println("Making Object " + obj);
        return obj;
    }

    // 3) 為這個類添加一個void activateObject(Object obj)方法。這個方法用於將對象“激活”——設置為適合開始使用的狀態。
    public void activateObject(Object obj) throws Exception {
        System.err.println("Activating Object " + obj);
    }

    // 4) 為這個類添加一個void passivateObject(Object obj)方法。這個方法用於將對象“掛起”——設置為適合開始休眠的狀態。
    public void passivateObject(Object obj) throws Exception {
        System.err.println("Passivating Object " + obj);
    }

    // 5) 為這個類添加一個boolean validateObject(Object obj)方法。這個方法用於校驗一個具體的對象是否仍然有效,已失效的對象會被自動交給destroyObject方法銷毀。
    public boolean validateObject(Object obj) {
        /* 以1/2的概率將對象判定為失效 */
        boolean result = (Math.random() > 0.5);
        System.err.println("Validating Object "
                + obj + " : " + result);
        return result;
    }

    // 6) 為這個類添加一個void destroyObject(Object obj)方法。這個方法用於銷毀被validateObject判定為已失效的對象。
    public void destroyObject(Object obj) throws Exception {
        System.err.println("Destroying Object " + obj);
    }

}

 

    ObjectPool

 

  有了合適的PooledObjectFactory之后,便可以開始請出ObjectPool來與之同台演出了。 
ObjectPool是在org.apache.commons.pool包中定義的一個接口,實際使用的時候也需要利用這個接口的一個具體實現。Pool組件本身包含了若干種現成的ObjectPool實現,

StackObjectPool :

StackObjectPool利用一個java.util.Stack對象來保存對象池里的對象。這種對象池的特色是: 

    • 可以為對象池指定一個初始的參考大小(當空間不夠時會自動增長)。
    • 在對象池已空的時候,調用它的borrowObject方法,會自動返回新創建的實例。
    • 可以為對象池指定一個可保存的對象數目的上限。達到這個上限之后,再向池里送回的對象會被自動送去回收。

SoftReferenceObjectPool 

SoftReferenceObjectPool利用一個java.util.ArrayList對象來保存對象池里的對象。不過它並不在對象池里直接保存對象本身,而是保存它們的“軟引用”(Soft Reference)。這種對象池的特色是: 

    • 可以保存任意多個對象,不會有容量已滿的情況發生。
    • 在對象池已空的時候,調用它的borrowObject方法,會自動返回新創建的實例。
    • 可以在初始化同時,在池內預先創建一定量的對象。
    • 當內存不足的時候,池中的對象可以被Java虛擬機回收。

GenericObjectPool----jedis就是基於這個的

GenericObjectPool利用一個org.apache.commons.collections.CursorableLinkedList對象來保存對象池里的對象。這種對象池的特色是: 

    • 可以設定最多能從池中借出多少個對象。
    • 可以設定池中最多能保存多少個對象。
    • 可以設定在池中已無對象可借的情況下,調用它的borrowObject方法時的行為,是等待、創建新的實例還是拋出異常。
    • 可以分別設定對象借出和還回時,是否進行有效性檢查。
    • 可以設定是否使用一個單獨的線程,對池內對象進行后台清理。

 

 

可以直接利用。如果都不合用,也可以根據情況自行創建。具體的創建方法,可以參看Pool組件的文檔和源碼。

  我這邊總結了下,對代碼做了一個簡化的修改

  我們可以看到,這個對象池就跟我們之前猜想的站點的功能一樣,主要的方法就是一個借borrowObject(),還returnObject()功能。

 

import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.PoolableObjectFactory;
import org.apache.commons.pool.impl.StackObjectPool;

public class ObjectPoolSample {

    public static void main(String[] args) {
        Object obj = null;

        // 1) 生成一個要用的PoolableObjectFactory類的實例。
        PoolableObjectFactory factory = new PoolableObjectFactorySample();

        // 2) 利用這個PoolableObjectFactory實例為參數,生成一個實現了ObjectPool接口的類(例如StackObjectPool)的實例,作為對象池。
        ObjectPool pool = new StackObjectPool(factory);

        try {
            for(long i = 0; i < 100 ; i++) {
                System.out.println("== " + i + " ==");

                // 3) 需要從對象池中取出對象時,調用該對象池的Object borrowObject()方法。
                obj = pool.borrowObject();

                System.out.println(obj);

                // 4) 需要將對象放回對象池中時,調用該對象池的void returnObject(Object obj)方法。
                pool.returnObject(obj);

            }
            obj = null;//明確地設為null,作為對象已歸還的標志
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            try{
                if (obj != null) {//避免將一個對象歸還兩次
                    pool.returnObject(obj);
                }

                // 5) 當不再需要使用一個對象池時,調用該對象池的void close()方法,釋放它所占據的資源。
                pool.close();

            }
            catch (Exception e){
                e.printStackTrace();
            }
        }
    }

}

 

 ObjectPoolFactory
有時候,要在多處生成類型和設置都相同的ObjectPool。如果在每個地方都重寫一次調用相應構造方法的代碼,不但比較麻煩,而且日后修改起來,也有所不便。這種時候,正是使用ObjectPoolFactory的時機。 
ObjectPoolFactory是一個在org.apache.commons.pool中定義的接口,它定義了一個稱為ObjectPool createPool()方法,可以用於大量生產類型和設置都相同的ObjectPool。 

 

public static void main(String[] args) {  
        Object obj = null;  
        PoolableObjectFactory factory = new PoolableObjectFactorySample();  
        ObjectPoolFactory poolFactory = new StackObjectPoolFactory(factory);  
        ObjectPool pool = poolFactory.createPool();  
        try {  
            for(long i = 0; i < 100 ; i++) {  
                System.out.println("== " + i + " ==");  
                obj = pool.borrowObject();  
                System.out.println(obj);  
                pool.returnObject(obj);  
            }  
            obj = null;  
        }  
        catch (Exception e) {  
            e.printStackTrace();  
        }  
        finally {  
            try{  
                if (obj != null) {  
                    pool.returnObject(obj);  
                }  
                pool.close();  
            }  
            catch (Exception e){  
                e.printStackTrace();  
            }  
        }  
    }  

 

 

 

結束語 
恰當地使用對象池化,可以有效地降低頻繁生成某些對象所造成的開銷,從而提高整體的性能。而借助Apache Commons Pool組件,可以有效地減少花在處理對象池化上的工作量,進而,向其它重要的工作里,投入更多的時間和精力。 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM