定义
对象的实例化是最耗费性能的操作之一,这在过去是个大问题,现在不用再过分关注它。但当我们处理封装外部资源的对象(例如数据库连接)时,对象的创建操作则会耗费很多资源。解决方案是重用和共享这些创建成本高昂的对象,这称为对象池模式。
这其实就是说,有些资源我可能获取起来不容易,如果频繁的获取就会特别耗费资源,因此,我获取完后想尽可能的重用他们,以减少我获取的次数,从而节约资源和提升性能,由此,引入了对象池这一说。
写到这里,越来越发现,其实这些所谓的设计模式,我们在平时就已经使用过了,比如这里的模式其实就是我们常用的数据库连接池这个工具,只是使用的时候还不知道这就是一种设计模式,到现在我们才知道,哦,原来我用的这个东西背后有这么一个模式啊。
组成
- 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);
}
}