您的位置:首页 > 编程语言 > Java开发

Java程序性能优化 读书笔记(九)优化组件:池

2017-10-31 21:49 441 查看
一、对象池化

对象池化,是目前非常常用的一种系统优化技术。它的核心思想是,如果一个类被频繁请求使用,那么不必每次都生成一个实例,可以将这个类的一些实例保存在一个"池"中,待需要使用的时候直接从池中获取。这个"池"就称为对象池。在实现细节上,它可能是一个数组,一个链表或者任何集合类。

对象池的使用非常广泛,其中最为大家所熟悉的,就是线程池数据库连接池

线程池中,保存着可以被重用的线程对象,当有任务被提交到线程池时,系统并不需要新建线程,而是从池中获得一个可用的线程,执行这个任务。在任务结束后,也不关闭线程,而将它返回到池中,以便下次继续使用。由于线程的创建和销毁是较为费时的工作,因此,在线程调度频繁的系统中,线程池可以很好地改善性能。

数据库连接池也是一种特殊的对象池,它用于维护数据库连接的集合。当系统需要访问数据库时,不需要重新建立数据库连接,而可以直接从池中获取;在数据库操作完成后,也不关闭数据库连接,而是将连接返回到连接池中。由于数据库连接的创建和销毁是重量级的操作,因此,避免频繁进行这两个操作,对改善系统的性能也有积极意义。

在程序中使用数据库连接池和线程池,可以有效地改善系统在高并发下的性能。这是两个非常重要的性能组件。任何对性能敏感的系统,都需要考虑合理配置这两个组件。

二、数据库连接池

目前应用较为广泛的数据库连接池组件有C3P0和Proxool。其中C3P0是伴随着Hibernate一起发布,与Hibernate联系紧密的数据库连接池。本文以C3P0为例,展示数据库连接池的一般使用方法和特性。

若在Hibernate中使用C3P0连接池,只需要将C3P0的jar包复制到开发环境中,并且在hibernate.cfg.xml中加入一些配置项即可:

<property name="connection.provider_class">org.hibernate.connection.
C3P0ConnectionProvider</property>
<property name="connection.autoReconnect">true</property>
<property name="connection.autoReconnectForPools">true</property>
<property name="connection.is-connection-validation-required">true
</property>
<!-- 最大连接数 -->
<property name="hibernate.c3p0.max_size">20</property>
<!-- 最小连接数 -->
<property name="hibernate.c3p0.min_size">5</property>
<!-- 获得连接的超时时间,如果超过这个时间,会抛出异常,单位毫秒 -->
<property name="hibernate.c3p0.timeout">120</property>
<!-- 最大的PreparedStatement的数量 -->
<property name="hibernate.c3p0.max_statements">100</property>
<!-- 每隔120秒检查连接池里的空闲连接,单位是秒-->
<property name="hibernate.c3p0.idle_test_period">120</property>
<!-- 当连接池里的连接用完时,C3P0一次性获取的新的数据库连接数 -->
<property name="hibernate.c3p0.acquire_increment">2</property>
<!-- 每次都验证连接是否可用 -->
<property name="hibernate.c3p0.validate">true</property>
当然,也可以脱离Hibernate单独在应用程序中使用C3P0。以下代码构造了一个C3P0的数据库连接池,并从中获得一个数据库连接:

DataSource unpooled = DataSources
.unpooledDataSource(
"jdbc:mysql://127.0.0.1:3306/test", //连接MySQL数据库
"root", ""); //这个不是连接池
DataSource pooled = DataSources.pooledDataSource(unpooled);
//构建了一个连接池
con = pooled.getConnection(); //从连接池中获取连接 为了能够从代码层面更好地理解数据库连接池,读者可以仔细阅读以下代码:
public static void main(String[] argv) {
try {
Class.forName("com.mysql.jdbc.Driver");
DataSource unpooled = DataSources
.unpooledDataSource(
"jdbc:mysql://127.0.0.1:3306/test",
"root", "");
DataSource pooled = DataSources.pooledDataSource(unpooled);
Connection con = null;
Statement stmt = null;
ResultSet rs = null;

con = pooled.getConnection();       //第一次取得数据库连接
System.out.println("con Class Type is:"+con.getClass().getName());
Object o1=getInnter(con);           //取得内部的实际数据库连接
System.out.println("Inner con Class Type is:"+o1.getClass().
getName());

stmt = con.createStatement();
rs = stmt.executeQuery("SELECT * FROM user");
while (rs.next())
System.out.println("Data from DB:"+rs.getString(1));
rs.close();
stmt.close();
con.close();

Thread.sleep(1000);                 //等待连接返回池中
con = pooled.getConnection();       //第二次取得数据库连接
Object o2=getInnter(con);
if(o1==o2)                          //相同,则说明数据库连接被复用
System.out.println("o1 and o2 is same object.");
stmt = con.createStatement();
rs = stmt.executeQuery("SELECT * FROM user");
while (rs.next())
System.out.println("Data from DB:"+rs.getString(1));
rs.close();
stmt.close();
con.close();

} catch (Exception e) {
e.printStackTrace();
}
}
public static Object getInnter(Object con){
Object re=null;
Field f;
try {
f = con.getClass().getDeclaredField("inner");
f.setAccessible(true);
re= f.get(con);                 //取得内部包装的Connection
f.setAccessible(false);
} catch Exception e) {
}
return re;
}
以上代码运行后,输出:
con Class Type is:com.mchange.v2.c3p0.impl.NewProxyConnection
Inner con Class Type is:com.mysql.jdbc.JDBC4Connection
Data from DB:1
o1 and o2 is same object.
Data from DB:1

上述代码中,首先从数据库连接池获得一个连接。发现连接类型并不是mysql的数据库连接,而是com.mchange.v2.c3p0.impl.NewProxyConnection。根据类名中可以推测,从数据库连接池中获得的连接只是一个代理。接着,通过反射,取得这个对象中名为inner的属性,并打印其Class类型,发现这才是真正的mysql连接。关闭NewProxyConnection连接,再向池中请求一个新的连接,同样获取该连接内部的实际数据库连接对象。发现,第一次使用的实际数据库连接对象o1和第二次使用的对象o2是完全相同的。

这说明,前后两次数据库连接的请求均返回了相同的数据库连接。关闭NewProxyConnection连接时,并没有真正关闭数据库连接,而只是将数据库连接放入连接池保存,使得数据库连接在连接池中得到了复用。而从连接池返回的NewProxyConnection对象,只是对真实数据库连接的包装。

除了线程池和数据库连接池,对于普通的Java对象,在必要的时候,也可以进行池化管理。对于那些经常使用,并且创建很费时的大型对象来说,使用对象池维护,不仅可以节省获得对象实例的成本,还可以减轻GC频繁回收这些对象产生的系统压力。但对于生成对象开销很小的对象进行池化,反而可能得不偿失,维护对象池的成本可能会大于对象池带来的好处。

注意:在JDK中,new操作的效率是相当高的,不需要担心频繁的new操作对系统有性能影响。但是new操作时所调用的类构造函数可能是非常费时的,对于这些对象,可以考虑池化。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: