一、概述
大多時候,我們獲取對象的方法都是直接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)類ProxiedObjectPool和ProxiedKeyedObjectPool,我們下面簡單介紹下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)來給我們使用:
- GenericObjectPool
- SoftReferenceObjectPool
- GenericKeyedObjectPool
- ProxiedObjectPool
- ProxiedKeyedObjectPool
一般情況下,GenericObjectPool足夠滿足我們大部分的場景,而且用起來也很簡單,我們只需要實現(xiàn)一個對象工廠類就可以了,可謂是開箱即用。另外SoftReferenceObjectPool和GenericKeyedObjectPool也有不少的使用場景,也很好用。
對于ProxiedObjectPool和ProxiedKeyedObjectPool,個人感覺并不太好用。因為代理對象只會在方法被調(diào)用前執(zhí)行一些自定義方法,而沒有在方法被調(diào)用后執(zhí)行一些方法。另外,要開啟代理對象池還需要專門設置AbandonedConfig,很是麻煩??赡苓€不如自己基于GenericObjectPool實現(xiàn)一個。當然,也可能是我理解不夠,如果有不同意見的讀者歡迎指出。
我的CSDN博客地址
https://blog.csdn.net/u013332124/article/details/81042375