实例池之惑

来源:互联网 发布:mac如何设置开机系统 编辑:程序博客网 时间:2024/04/28 17:58
    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)));
    }
   
}

  

       
         
原创粉丝点击