Archive for the ‘程序’ category

静态域的作用范围

August 25th, 2010

静态域的作用范围是在同一个类之内,类不仅有名称上的区别,还有classloader,即类加载器的问题,同一个class文件被不同的类加载器加载后,即便它们有相同的名称,但是它们并不是同一个类。因此在不同类加载器中同名类的静态变量是不共享的。下面做一个测试,来验证它。下面的Test类有一个静态域value,两个方法分别增加value的值和打印状态。

public class Test {
    private static int value = 1;
   
    public void increase() {
        value++;
    }
   
    public void print() {
        System.out.println("My Class Loader: "
            + this.getClass().getClassLoader() +  ", Value: " + value);
    }
}

下面的类StaticTest,运行时创建两个类加载器,分别载入同一个class文件,并通过反射依次调用print, increase,print方法。如果静态域在整个JVM内共享,那么value被加了两次,最后会输出3;否则应该输出2。由于类加载器会先向父类加载器请求类,找不到的时候才会自己加载,所以在运行这个测试时,一定不要把Test类先放到classpath中。

import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

public class StaticTest {
    public static void main(String[] args) throws Exception {
        URL[] classURLs = new URL[] {
            new URL("file:/home/sulong/tmp/")
        };
       
        URLClassLoader classLoader1 = new URLClassLoader(classURLs);
        URLClassLoader classLoader2 = new URLClassLoader(classURLs);
       
        test(classLoader1);
        System.out.println("--------------------");
        test(classLoader2);
    }
   
    private static void test(ClassLoader loader)
            throws Exception {
        Class clazz = loader.loadClass("Test");
        Object testObj = clazz.newInstance();
        invoke(clazz, testObj, "print");
        invoke(clazz, testObj, "increase");
        invoke(clazz, testObj, "print");
    }
   
    private static void invoke(Class clazz, Object obj, String name)
            throws Exception {
        Method method1 = clazz.getMethod(name, new Class[]{});
        method1.invoke(obj, new Object[]{});
    }
}

下面是我的机器上运行时的测试结果:

My Class Loader: java.net.URLClassLoader@7987aeca, Value: 1
My Class Loader: java.net.URLClassLoader@7987aeca, Value: 2
--------------------
My Class Loader: java.net.URLClassLoader@5d0385c1, Value: 1
My Class Loader: java.net.URLClassLoader@5d0385c1, Value: 2
  • Share/Bookmark

在Tomcat的多个WAR之间共享Spring

August 24th, 2010

由于非技术原因,公司的专门成立的支付部,招一批Java程序员,做了支付系统;又由于非技术原因,两个月内,这个部门从领导到程序员全走光了。然后这个支付系统就移交给我们这帮受苦受难的兄弟们维护了。在看他们代码的时候发现了很有趣的东西,就是在tomcat内共享Spring context。

1,实现方法

1.1 建立一个全局的jndi资源

这个全局的jndi资源代表了一个共享的spring上下文,每次通过jndi查找资源时,都返回同一个spring上下文实例。修改tomcat/conf/context.xml文件,加入以下代码:

    <Resource auth="Container"
       contextConfigLocation="/com/test/application-context.xml"
       factory="com.test.TomcatWebApplicationContextResourceFactory"
       name="bean/RootApplicationContextFactory"
       type="org.springframework.context.ApplicationContext"/>

上面这段配置的意思就是,当有人通过jndi名称”bean/RootApplicationContextFactory”来查找对象时,容器就新建一个TomcatWebApplicationContextResourceFactory类实例,并调用它的getObjectInstance方法来获得资源对象。这个工厂类要是想javax.naming.spi.ObjectFactory接口。代码如下:

package com.test;

import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.spi.ObjectFactory;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TomcatWebApplicationContextResourceFactory implements
        ObjectFactory {
    private static final String PARAM = "contextConfigLocation";
        private static final String DEFAULT = "application-context.xml";

    private static ApplicationContext context; //被共享的spring上下文

    private void init(String confFile) {
        context = new ClassPathXmlApplicationContext(confFile);
    }

    public Object getObjectInstance(Object obj,
                        Name name, Context nameCtx,
            Hashtable<?, ?> environment) throws Exception {
        if (null == context) {
            // Customize the bean properties from our attributes
            Reference ref = (Reference) obj;

                        //从xml配置文件里取得contextConfigLocation元素的值
            RefAddr addr = ref.get(PARAM);

            if (null != addr) {
                String value = (String) addr.getContent();
                init(value);
            } else {
                init(DEFAULT);
            }
        }
        return context;
    }
}

1.2 在web应用中引用上面定义的资源

修改tomcat/conf/web.xml,添加以下的内容,这样在这个tomcat下的所有的web应用就都可以访问共享的spring上下文了。

    <resource-env-ref>
        <description>Object factory for Root applicationcontext</description>
        <resource-env-ref-name> bean/RootApplicationContextFactory
        </resource-env-ref-name>
        <resource-env-ref-type> org.springframework.context.ApplicationContext
        </resource-env-ref-type>
    </resource-env-ref>

1.3 加入依赖的包

从第一步可以看出,tomcat容器需要访问spring的一些类,需要初始化上下文,所以要把初始化上下文时用到的类共享给tomcat。最简单的方法是把所有相关的类和jar包复制到tomcat/lib目录。还可以修改catalina.properties实现,如下:

shared.loader=/usr/local/jars/*.jar

上面的代码让tomcat载入自定目录里面所有的jar文件。

2 我的看法

一个庞大的spring上下文是要占用很多内存的,所有应用共享一个上下文,而不是每个web应用一份,就可以节省不少的内存。但是由此却带来了部署和配置的麻烦。如果有人利用这种机制,让多个web应用通过共享spring bean来实现一些奇怪的功能,结果导致这些应用间相互耦合,那么以后想拆分开也不容易了。总体来说,我不喜欢这样方案,但是如果真的很穷很缺内存的话,这也不失一个好方法。

  • Share/Bookmark

一次sql调优

July 20th, 2010

想知道我的Java应用程序在运行时到底执行了那些SQL,以及这些SQL运行时耗费多少时间。起初试图通过mysql的日志来查看,调查了一下发现很麻烦,于是就是用mysql的jdbc驱动程序提供的功能来查看,很方便。开启的方法很简单,就是在jdbc url上加上如下的参数:

jdbc:mysql://localhost/mydb?profileSQL=true

还有很多其它很有用的参数,如:

includeInnodbStatusInDeadlockExceptions=true

可以在发生innodb死锁时打印innodb status.详细情况看最新的mysql J connector 文档。

打开日志后,发现一般的sql语句的执行时间都< 1ms, 但是有一条sql的执行时间是180ms以上。这条sql是

SELECT max(oid) FROM inventory_entry WHERE sku =GROUP BY lot ;

这条sql是要查出相同sku记录按lot分组后最新的一条记录的主键。经查,因为lot在新业务模式下将不再被使用,所以可以把group by 去掉了,去掉之后执行时间降到100ms左右。select max(0id) 的作用是为了取得最新的记录,但是可以通过主键排序直接拿到的oid,因为oid是自增长的。改成:

SELECT oid FROM inventory_entry WHERE sku = ? ORDER BY oid DESC LIMIT 1;

后,时间降到1ms左右了,达到了性能要求。

  • Share/Bookmark

Servlet 3.0异步处理

July 14th, 2010

看过Servlet 3.0的规范和API后,可以看出来,所谓异步的HTTP,其实异步的不是HTTP,而是服务器端异步地处理HTTP请求,而HTTP客户端,仍旧是同步的等待服务端的处理结果。一般servlet容器会分配一个线程用来处理一个来自客户端的HTTP请求,在这个线程发送回HTTP响应之前,这个线程只属于这个HTTP请求不能离开处理其它请求。采用Servlet3.0之后,当前的线程可以开启异步处理,开启异步处理的时候会得到一个异步处理上下文对象,之后当前的线程就可以不生成HTTP响应而直接退出去处理其它的HTTP请求,其它线程可以在之后通过异步处理上下文来生成和发送那个HTTP响应。可见所谓的异步HTTP其实只是一种可以让当前的处理线程在不生成响应前就离开,而在之后再处理这个HTTP请求的机制。

从客户端看来,不论是哪种方式,浏览器都在发送完HTTP请求之后,都必须同步的等待服务器端的响应。假如浏览器发送完HTTP请求之后,可以在得到服务器处理结果之前转而处理其它事情,而在未来的某个时刻,当服务器处理完请求后,不需要客户端再发送请求,就可以发响应发回给浏览器,也许那才是真的异步HTTP了。但是这是违反HTTP的有请求才响应,无请求不响应的基本原则的。 HTTP长连接可以让客户端和服务器在同一个TCP连接中做多次请求响应,但是并不能改变客户端和服务器之间的同步请求响应模式。

尽管Servlet3.0的异步功能不能改变HTTP的协议,在本质上让浏览器和服务器之间异步的交互,但是这一功能还是有非常大的意义的。假设接受请求和发送响应的时间分别为Req和Resp,每个请求都要执行一个耗时P的操作O,并且O操作会让调用者阻塞,当在P时间内有n个请求发送过来时,用传统的处理方式,由于P时间内每个线程都不能处理完,servlet容器要分配F(n) = n个线程处理请求,如下图所示:

而用Servlet3.0的异步处理时,处理线程可以开启单一线程去做那个耗时P的操作,而把当前请求的异步处理上下文放入一个等待队列中,自己则接着处理其它的请求,假设这个开启异步,加入异步处理上下文的操作需要时间A,那么需要开启F(n) = n*(A+Req+Resp)/(P + Req + Resp) + 1个线程就可以在P时间内处理完所有请求。如下图所示:

假如执行操作O可以不阻塞,耗时C就返回, 那么n客户端每获得一次资源,需要发送f(n) = n*P / (Req + C + Resp) 次请求,而用异步处理的时候,只需要n次请求。可见当A足够小于P时,O阻塞访问时,异步可以用更少的线程处理更多的请求;O非阻塞访问时,异步可以减少请求次数。

以web QQ为例来看看。用户发送消息时,假设服务器分发消息耗时P远远大于开启异步和把消息放到待分发队列的耗时A,那么采用异步处理发送消息,可以用更少的线程处理更多的发送消息请求。用户接受消息,假设平均P时间内用户才有新消息到达,而检查一次新消息的耗时远小于P,那么采用异步则可以减少很多客户端请求。

  • Share/Bookmark