我們在服務器開發的過程中,往往會有一些對象,它的創建和初始化需要的時間比較長,比如數據庫連接,網絡IO,大數據對象等。在大量使用這些對象時,如果不采用一些技術優化,就會造成一些不可忽略的性能影響。一種辦法就是使用對象池,每次創建的對象並不實際銷毀,而是緩存在對象池中,下次使用的時候,不用再重新創建,直接從對象池的緩存中取即可。為了避免重新造輪子,我們可以使用優秀的開源對象池化組件apache-common-pool2,它對對象池化操作進行了很好的封裝,我們只需要根據自己的業務需求重寫或實現部分接口即可,使用它可以快速的創建一個方便,簡單,強大對象連接池管理類。
一,common-pool2簡介
首先是下載這個組件,使用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使用的是面向接口的編程,它為我們提供的是一個抽象的對象池管理方式,所以根據我們業務的不同,我們需要重寫或實現一些方法和接口,我們一個一個看一下。
1,GenericObjectPool
這個是對象池實現的核心類,它實現了對對象池的管理,是一個基本的對象池實現,一般情況下,我們可以直接使用。在使用這個類的時候,我們需要傳入兩個重要的參數:GenericObjectPoolConfig類和PooledObjectFactory接口的實現,一會我們再詳細說這兩個。
在GenericObjectPool中,有兩個我們會用到的方法:
public T borrowObject() throws Exception 從對象池中獲取一個對象
public void returnObject(T obj) 對象使用完之后,歸還到對象池,
其它還有一些方法,比如關閉對象池,銷毀對象池,獲取對象池中空閑的對象個數等,可以自行查看API。
這個接口是我們要實現的,它對要實現對象池化的對象做了一些管理。這個工廠接口就是為了讓我們根據自己的業務創建和管理要對象池化的對象。
PooledObject<T> makeObject() throws Exception;
這個方法是用來創建一個對象,當在GenericObjectPool類中調用borrowObject方法時,如果當前對象池中沒有空閑的對象,GenericObjectPool會調用這個方法,創建一個對象,並把這個對象封裝到PooledObject類中,並交給對象池管理。
void destroyObject(PooledObject<T> p) throws Exception;
銷毀對象,當對象池檢測到某個對象的空閑時間(idle)超時,或使用完對象歸還到對象池之前被檢測到對象已經無效時,就會調用這個方法銷毀對象。對象的銷毀一般和業務相關,但必須明確的是,當調用這個方法之后,對象的生命周期必須結果。如果是對象是線程,線程必須已結束,如果是socket,socket必須已close,如果是文件操作,文件數據必須已flush,且文件正常關閉。
boolean validateObject(PooledObject<T> p);
檢測一個對象是否有效。在對象池中的對象必須是有效的,這個有效的概念是,從對象池中拿出的對象是可用的。比如,如果是socket,那么必須保證socket是連接可用的。在從對象池獲取對象或歸還對象到對象池時,會調用這個方法,判斷對象是否有效,如果無效就會銷毀。
void activateObject(PooledObject<T> p) throws Exception;
激活一個對象或者說啟動對象的某些操作。比如,如果對象是socket,如果socket沒有連接,或意外斷開了,可以在這里啟動socket的連接。它會在檢測空閑對象的時候,如果設置了測試空閑對象是否可以用,就會調用這個方法,在borrowObject的時候也會調用。另外,如果對象是一個包含參數的對象,可以在這里進行初始化。讓使用者感覺這是一個新創建的對象一樣。
void passivateObject(PooledObject<T> p) throws Exception;
鈍化一個對象。在向對象池歸還一個對象是會調用這個方法。這里可以對對象做一些清理操作。比如清理掉過期的數據,下次獲得對象時,不受舊數據的影響。
一般來說activateObject和passivateObject是成對出現的。前者是在對象從對象池取出時做一些操作,后者是在對象歸還到對象池做一些操作,可以根據自己的業務需要進行取舍。
3,帶Key的對象池GenericKeyedObjectPool
這種對象池和前面的GenericObjectPool對象池操作是一樣的,不同的是對應的每個方法帶一個key參數。你可以把這個GenericKeyedObjectPool的對象池看作是一個map的GenericObjectPool,每個key對應一個GenericObjectPool。它用於區別不同類型的對象。比如數據庫連接,有可能會連接到不同地址的數據庫上面。就可以用這個區分。
4,參數配置類GenericObjectPoolConfig
這個類允許使用者對對象池的一些參數進行調整,根據需要定制對象池。下面說逐一說一下每個參數的含義。
lifo :對象池存儲空閑對象是使用的LinkedBlockingDeque,它本質上是一個支持FIFO和FILO的雙向的隊列,common-pool2中的LinkedBlockingDeque不是Java原生的隊列,而有common-pool2重新寫的一個雙向隊列。如果為true,表示使用FIFO獲取對象。默認值是true.建議使用默認值。
fairness:common-pool2實現的LinkedBlockingDeque雙向阻塞隊列使用的是Lock鎖。這個參數就是表示在實例化一個LinkedBlockingDeque時,是否使用lock的公平鎖。默認值是false,建議使用默認值。
maxWaitMillis:當沒有空閑連接時,獲取一個對象的最大等待時間。如果這個值小於0,則永不超時,一直等待,直到有空閑對象到來。如果大於0,則等待maxWaitMillis長時間,如果沒有空閑對象,將拋出NoSuchElementException異常。默認值是-1;可以根據需要自己調整,單位是毫秒。
minEvictableIdleTimeMillis:對象最小的空閑時間。如果為小於等於0,最Long的最大值,如果大於0,當空閑的時間大於這個值時,執行移除這個對象操作。默認值是1000L * 60L * 30L;即30分鍾。這個參數是強制性的,只要空閑時間超過這個值,就會移除。
softMinEvictableIdleTimeMillis:對象最小的空間時間,如果小於等於0,取Long的最大值,如果大於0,當對象的空閑時間超過這個值,並且當前空閑對象的數量大於最小空閑數量(minIdle)時,執行移除操作。這個和上面的minEvictableIdleTimeMillis的區別是,它會保留最小的空閑對象數量。而上面的不會,是強制性移除的。默認值是-1;
numTestsPerEvictionRun:檢測空閑對象線程每次檢測的空閑對象的數量。默認值是3;如果這個值小於0,則每次檢測的空閑對象數量等於當前空閑對象數量除以這個值的絕對值,並對結果向上取整。
testOnCreate:在創建對象時檢測對象是否有效,true是,默認值是false。
testOnBorrow:在從對象池獲取對象時是否檢測對象有效,true是;默認值是false。
testOnReturn:在向對象池中歸還對象時是否檢測對象有效,true是,默認值是false。
testWhileIdle:在檢測空閑對象線程檢測到對象不需要移除時,是否檢測對象的有效性。true是,默認值是false。
timeBetweenEvictionRunsMillis:空閑對象檢測線程的執行周期,即多長時候執行一次空閑對象檢測。單位是毫秒數。如果小於等於0,則不執行檢測線程。默認值是-1;
blockWhenExhausted:當對象池沒有空閑對象時,新的獲取對象的請求是否阻塞。true阻塞。默認值是true;
maxTotal:對象池中管理的最多對象個數。默認值是8。
maxIdle:對象池中最大的空閑對象個數。默認值是8。
minIdle:對象池中最小的空閑對象個數。默認值是0。
以上就是common-pool2對象池的配置參數,使用的時候可以根據自己的需要進行調整。
5,common-pool2的應用
使用common-pool2的對象池技術的一個完美例子就是redis的Java客戶端JedisPool。大家可以下載Jedis的包,查看源碼進行學習。下次我將分享一個我使用common-pool2實現的一個thrift客戶端調用的連接池實現。
綜上所述,使用common-pool2可以快速的創建一個安全,強大,簡單的對象池管理類。它的開源性使它的功能得到了眾多項目的檢測,是非常安全的。在我們的業務中,如果有需要使用對象池化的操作,可以使用common-pool2快速實現。