JAVA世界有一些开源工程是提供了实例池,应用服务器也把实例池作为一个卖点,但实例池真的有用么?
事实上,我们都知道,Servlet就是一个典型的单实例,多线程模型。
在web服务器中,只有一个Servlet实例,而有多个并发线程访问这个实例。
在我刚做完的一个项目中,我们的业务逻辑层和DAO层的bean都是单例的,经过压力测试,没有任何问题。
其实,在项目实践中,我想我们大多数开发者用得最多的实例池就是连接池了,对于数据库连接这样的需要比较大的资源才能创建的对象Connection,我们做一个实例池显然是必要的,事实上连接池也是java世界应用服务器的一个标准“配件”,即DataSource。我们可以通过配置服务器的方式,配置一个DataSource,在普通的JDBC为基础的应用系统中,我指的是没有采用任何O/R Mapping框架的应用,而是直接通过JDBC编程实现的系统中,我们可以做一个DbUtil的类,提供简单的取得连接,释放连接等功能的工具类。
连接可以直接通过JNDI从服务器的context上取得。
应用分层,代码重用是我们设计业务系统最基本的要求,所以,我们的架构需要分层次,每个层级完成其专有的功能。下层为上层代码服务。这就出现一个问题,基于MVC的基本模式,我们的控制对象将要创建业务对象,业务对象同样也需要创建DAO对象来完成数据的持久化工作。
这样,在运行时,当并发数提高到一定程度时,JVM将执行大量的构造对象和销毁对象的工作。无疑,这对系统性能将产生比较大的影响。
所以,实例池的提法和实现也应运而生。
实例池被认为是一种简单而优雅的保存内存数据,提高系统速度的办法,通过共享和重用对象,进程或线程能够避免堕入不停的初始化和加载新对象,而垃圾收集过频的泥潭。
我想先分析一下在系统设计中为什么我们要创建对象,这个问题可能稍稍接触OOP的人都能回答。在并发访问时,同一时间点会有多个线程进入同一个对象的同一个方法,也会有多个线程同时访问同一个成员变量(属性/field)。这就会带来严重问题。比如:有一个Account对象,
package com.blog.shinepang;
/**
* @author ship
*
* 更改所生成类型注释的模板为
* 窗口 > 首选项 > Java > 代码生成 > 代码和注释
*/
public class Account {
private int current;
/**
* 加
* @param toAdd
*/
public void add(int toAdd) {
setCurrent(getCurrent() + toAdd);
}
/**
* 减
* @param toRemove
*/
public void remove(int toRemove) {
setCurrent(getCurrent() - toRemove);
}
/**
* @return
*/
public int getCurrent() {
return current;
}
/**
* @param i
*/
public void setCurrent(int i) {
current = i;
}
}
这个类就有一个属性current,当同时有多个并发线程访问此类的add/remove方法时,就可能出现不正常的逻辑。经过测试,当有5000个并发线程时,每个线程对current值的存取基本是对的,但当并发线程数达到10000时,就会出现很多不合逻辑的值。
但是如果创建的类没有任何属性(成员变量)呢?如果一个类没有成员变量,在单实例多线程的调用模式下,则不会出现并发线程访问同一变量的情况.
但这会不会带来问题呢?试想我们在业务应用中,不可避免要写很多工具类,比如DateUtil,StringUtil,ParamUtil等等.根据这些工具的类的名字,我们大约也可以猜测出它们的用法,及分别处理日期格式,字符串处理和参数处理的工具.
在每个工具类中,我们会写很多static方法.static方法在类加载过程中就会被创建,而任何类实例无关.实际上也是单实例,多线程模型. 请看如下代码:
package com.ship.intancepoolpuzzle;
public class MathUtil{
/**
* 计算从i到j相加的值
* @param i
* @param j
* @return
*/
public static int sum(int i, int j) {
int k = 0;
int sum = 0;
for (k = i; k <= j; k++) {
sum += k;
}
return sum;
}
}
package com.ship.intancepoolpuzzle;
import java.util.Random;
public class TestThread implements Runnable{
public static void main(String [] args){
TestThread test = new TestThread();
for(int i = 0;i<100;i++){
Thread thread = new Thread(test);
thread.start();
}
}
public void run() {
Random random =new Random();
System.out.println(Thread.currentThread());
System.out.println(MathUtil.sum(1,random.nextInt(100)));
}
}