定義
對象的實例化是最耗費性能的操作之一,這在過去是個大問題,現在不用再過分關注它。但當我們處理封裝外部資源的對象(例如數據庫連接)時,對象的創建操作則會耗費很多資源。解決方案是重用和共享這些創建成本高昂的對象,這稱爲對象池模式。
這其實就是說,有些資源我可能獲取起來不容易,如果頻繁的獲取就會特別耗費資源,因此,我獲取完後想盡可能的重用他們,以減少我獲取的次數,從而節約資源和提升性能,由此,引入了對象池這一說。
寫到這裏,越來越發現,其實這些所謂的設計模式,我們在平時就已經使用過了,比如這裏的模式其實就是我們常用的數據庫連接池這個工具,只是使用的時候還不知道這就是一種設計模式,到現在我們才知道,哦,原來我用的這個東西背後有這麼一個模式啊。
組成
- ResourcePool(資源池類):用於封裝邏輯的類。用來保存和管理資源列表。
- Resource(資源類):用於封裝特定資源的類。資源類通常被資源池類引用,因此只要資源池不重新分配,它們就永遠不會被回收
- Client(客戶端類):使用資源的類。
流程
- 當Client 需要資源的時候,會向ResourcePool 索要一個可用資源,這些資源就是Resource
- Client 使用資源完成後釋放這個資源到ResourcePool
- 釋放後的這個資源又可以給別的應用調用。
Redis連接池示例
爲了方便說明,我這裏用 Redis 的連接池來說明一下。
- Redis 連接池的代碼如下:
public class JedisClient {
public static JedisPool jedisPool = null;
/**
* 初始化Redis連接池
*/
static {
if(jedisPool == null){
JedisPoolConfig config = new JedisPoolConfig();
/*// 連接耗盡時是否阻塞, false報異常,ture阻塞直到超時, 默認true
config.setBlockWhenExhausted(true);
// 設置的逐出策略類名, 默認DefaultEvictionPolicy(當連接超過最大空閒時間,或連接數超過最大空閒連接數)
config.setEvictionPolicyClassName("org.apache.commons.pool2.impl.DefaultEvictionPolicy");
// 是否啓用pool的jmx管理功能, 默認true
config.setJmxEnabled(true);
// 最大空閒連接數, 默認8個 控制一個pool最多有多少個狀態爲idle(空閒的)的jedis實例。
config.setMaxIdle(8);
config.setMaxTotal(100);
// 表示當borrow(引入)一個jedis實例時,最大的等待時間,如果超過等待時間,則直接拋出JedisConnectionException;
config.setMaxWaitMillis(100000);
config.setTestOnReturn(true);*/
config.setMaxTotal(200);
config.setMaxIdle(50);
config.setMinIdle(8);//設置最小空閒數
config.setMaxWaitMillis(10000);
config.setTestOnBorrow(true);
config.setTestOnReturn(true);
//Idle時進行連接掃描
config.setTestWhileIdle(true);
//表示idle object evitor兩次掃描之間要sleep的毫秒數
config.setTimeBetweenEvictionRunsMillis(30000);
//表示idle object evitor每次掃描的最多的對象數
config.setNumTestsPerEvictionRun(10);
//表示一個對象至少停留在idle狀態的最短時間,然後才能被idle object evitor掃描並驅逐;這一項只有在timeBetweenEvictionRunsMillis大於0時纔有意義
config.setMinEvictableIdleTimeMillis(60000);
String serverAddress = Play.configuration.getProperty("redis.server.address");
String password = Play.configuration.getProperty("redis.server.password");
int serverPort = Integer.valueOf(Play.configuration.getProperty("redis.server.port"));
int serverDatabase = Integer.valueOf(Play.configuration.getProperty("redis.server.database"));
jedisPool = new JedisPool(config, serverAddress, serverPort, 10000, password,serverDatabase);
}
}
- 使用連接池的時候
/**
* 獲取Jedis實例
* @return
*/
public synchronized static Jedis getJedis() {
try{
if (jedisPool != null) {
Jedis resource = jedisPool.getResource();
return resource;
}
}catch(Exception e){
e.printStackTrace();
}
return null;
}
- 釋放連接池的時候
/**
* 釋放jedis資源
* @param jedis
*/
public static void close(final Jedis jedis) {
if (jedis != null) {
jedis.close();
}
}
總結
對象池模式是一個運用特別廣泛的模式。幾乎所有的中間件都可以用這個 模式進行資源的調用和釋放,還有許多大型數據庫都是支持連接池模式的。各位小夥伴,你們平時是怎麼使用它的?
附:傳送門
- Mysql數據庫連接池運行思路
服務器啓動時會建立一定數量的連接,並一直維持不少於這個數目的連接。客戶端需要連接的時候,連接池驅動會返回一個沒使用的連接池連接並把它標記爲忙。如果沒有空閒的連接,連接驅動會根據你的連接池配置重新建立一些連接。當使用完這個連接時,連接驅動會將這個連接重新標誌爲可用。現方式,返回的 Connection是原始Connection的代理,代理Connection的close方法不是真正關連接,而是把它代理的 Connection 對象還回到連接池中。 - 數據庫連接池代碼示例
public void demo(){
// 獲得連接:
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try{
// 創建連接池:
ComboPooledDataSource dataSource = new ComboPooledDataSource();
// 設置連接池的參數:
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://test");
dataSource.setUser("root");
dataSource.setPassword("1234");
dataSource.setMaxPoolSize(20);
dataSource.setInitialPoolSize(3);
// 獲得連接:
conn = dataSource.getConnection();
// 編寫Sql:
String sql = "select * from user";
// 預編譯SQL:
pstmt = conn.prepareStatement(sql);
// 設置參數
// 執行SQL:
rs = pstmt.executeQuery();
while(rs.next()){
System.out.println(rs.getInt("uid")+" "+rs.getString("username")+" "+rs.getString("password")+" "+rs.getString("name"));
}
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtils.release(rs, pstmt, conn);
}
}