JAVA程序性能优化--笔记2、设计模式:单例与代理

单例模式

单例模式是设计模式中使用最为普遍的模式之一。它是一种对象创建模式,用于产生一个对象的具体实例,它可以确保系统中一个类只产生一个实例。
Java语言中,这样的行为能带来两大好处: 
(1)对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销。 
(2)由于new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻GC压力,缩短GC停顿时间。

最简单的单例实现:
public class Singleton {
    private Singleton(){
      System.out.println("Singleton is create");  
    }

    private static Singleton instance = new Singleton();
    public static Singleton getInstance() {
        return instance;
    } 
}

为了确保系统中只有一个单例实例,构造函数的访问权限必须为private;单例实例通过静态成员获取。这种单例的实现方式非常简单,而且十分可靠。它唯一的不足仅是无法对instance实例做延迟加载。只是因为在单例实现中由于instance成员变量是static定义的,因此在JVM加载单例类时,单例对象就会被建立。如果此时单例类还扮演其他角色,那么将导致任何使用这单例类的地方都会初始化这个单例变量。

多角色单例类:
public class MultiRoleSingleton {
    private Singleton() {
        System.out.println("Singleton is create");
    }

    private static Singleton instance = new Singleton();
    public static Singleton getInstance() {
        return instance;
    }

    public static void createString(){
        System.out.println("createString in Singleton");
    }
}

此时,单例类除了创建单例实例外,还可以创建字符串。如果在调用Singleton.createString()创建字符串时,依然会先创建单例实例;虽然此时并没有用到。为此可以采用延迟加载机制避免多角色单例类在不必要的时候创建单例类。

懒加载单例类:
public class LazySingleton {
        private LazySingleton(){
            System.out.println("LazySingleton is create");
        }
        private static LazySingleton instance = null;
        public static synchronized LazySingleton getInstance() {
        if (instance==null)
            instance=new LazySingleton();
        return instance;
    }
}

虽然实现了延迟加载的功能,但和第一种方法相比,它引入了同步关键字,因此在多线程环境中,它的时耗要远远大于第一种单例模式。如果即使用延迟机制有不影响效率,可以考虑通过内部类来维护单例。单例模式使用内部类来维护单例的实例,当StaticSingleton被加载时,其内部类并不会被初始化,故可以确保当StaticSingleton类被载入JVM时,不会初始化单例类,
而当getInstance()方法被调用时,才会加载SingletonHolder,从而初始化instance。同时,由于实例的建立是在类加载时完成,故天生对多线程友好,getInstance()方法也不需要使用同步关键字。因此,这种实现方式同时兼备以上两种实现的优点。

内部类实现单例:

public class StaticSingleton {
    private StaticSingleton(){
        System.out.println("StaticSingleton is create");
    }
    private static class SingletonHolder {
        private static StaticSingleton instance = new StaticSingleton();
    }

    public static StaticSingleton getInstance() {
        return SingletonHolder.instance;
    }
    public static void createString(){
        System.out.println("createString in Singleton");
    }
}

使用内部类的方式实现单例,既可以做到延迟加载,也不必使用同步关键字,是一种比较完善的实现。通常采用上面的三种方式可以确保在系统中只存在唯一实例。但是如果在代码中,通过反射机制强行调用单例类的私有构造函数,可以生成多个实例。
序列化和反序列化可能会破坏单例。一般来说,对单例进行序列化和反序列化的场景并不多见,但是如果出现就必须注意。

序列化或反序列化场景下防止出现多个单例实例:
public class SerSingleton implements java.io.Serializable{
    String name;
    private SerSingleton() {
        System.out.println("Singleton is create");
        name="SerSingleton";
    }

    private static SerSingleton instance = new SerSingleton();
    public static SerSingleton getInstance() {
        return instance;
    }

    public static void createString(){
        System.out.println("createString in Singleton");
    }
    
    private Object readResolve(){  
        return instance;  
    }  

}

       理解SerSingleton需要了解序列化和反序列化的相关理论。后续将作出进一步说明。此处只需要牢记:当实现了私有的readReslove()方法后,readObject()已经形同虚设,它直接使用readReslove()替换了原本的返回值,从而在形式上构造了单例。

代理模式

静态代理

在软件设计中,使用代理模式的意图也很多,比如因为安全原因,需要屏蔽客户端直接访问真实对象;或者在远程调用中,需要使用代理类处理远程方法调用的技术细节(如RMI);也可能是为了提升系统性能,对真实对象进行封装,从而达到延迟加载的目的。

代理模式的结构:主题接口、真实主题、代理类、客户端。
以延迟加载为例,演示静态代理:
主题接口:
public interface IDBQuery {
    String request();
}
真实主题:
public class DBQuery implements IDBQuery{
    public DBQuery(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    @Override
    public String request() {
        return "request string";
    }
}
代理类:
public class DBQueryProxy implements IDBQuery {
    private DBQuery real=null; 
    @Override
    public String request() {
        if(real==null)
            real=new DBQuery();
        return real.request();
    }
}
客户端:
public class Main {
    public static void main(String args[]){
        IDBQuery q=new DBQueryProxy();
        q.request();
    }
}

延迟加载的核心思想是:如果当前并没有使用这个组件,则不需要真正地初始化它,使用一个代理对象替代它的原有的位置,只要在真正需要使用的时候,才对它进行加载。


动态代理:

动态代理是指在运行时动态生成代理类,即代理类的字节码将在运行时生成并载入当前的ClassLoader。相对于静态代理的好处:首先,不需要为真实主题写一个形式上完全一样的封装类,假如主题接口中的方法很多,为每一个接口写一个代理方法也是非常烦人的事,如果接口有变动,则真实主题和代理类都要修改,不利于系统维护;其次,使用一些动态代理的生成方法甚至可以在运行时指定代理类的执行逻辑,从而大大提升系统的灵活性。

动态代理使用字节码动态生成加载技术,在运行时生成并加载类。生成动态代理类的方法很多,如,JDK自带的动态代理、CGLIB(Code Generator Library)、Javassist或者ASM库。CGLIB和Javassist都是高级的字节码生成库,总体性能比JDK自带的动态代理好,而且功能十分强大。ASM是低级的字节码生成工具,使用ASM已经近乎于在使用Java bytecode编程,对开发人员要求最高,且可维护性较差,当然,也是性能最好的一种动态代理生成工具。推荐CGLIB或者Javassist。

使用JDK动态代理生成代理对象,内部逻辑与DBQueryProxy相类似,在真正使用时调用。

public class JdkDbQeuryHandler implements InvocationHandler {
    IDBQuery real=null;
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        if(real==null)
            real=new DBQuery();
        return real.request();
    }
}

以上代码生成一个实现了IDBQuery接口的代理类,代理类的内部逻辑由JdkDbQeuryHandler决定。生成代理类后,由newProxyInstance()方法返回该代理类的一个实例。

使用CGLIB动态代理生成代理对象:
//实现切入器,定义代理逻辑
public class CglibDbQueryInterceptor implements MethodInterceptor {
    IDBQuery real=null;
    @Override
    public Object intercept(Object arg0, Method arg1, Object[] arg2,
            MethodProxy arg3) throws Throwable {
        if(real==null)
            real=new DBQuery();
        return real.request();    
    }
}

 

使用Javassist生成代理类:可以使用两种方式:一种是使用代理工厂创建,另一种通过使用动态代码创建。

方式1、代理工厂创建:
public class JavassistDynDbQueryHandler implements MethodHandler {
    IDBQuery real=null;
    @Override
    public Object invoke(Object arg0, Method arg1, Method arg2, Object[] arg3)
            throws Throwable {
        if(real==null)
            real=new DBQuery();
        return real.request();    
    }
}

方式2、工厂代理:
Javassist使用动态Java代码创建代理的过程和工厂代理模式有不同。Javassist内部可以通过动态Java代码,生成字节码。
这种方式创建的动态代理可以非常灵活,甚至可以在运行时生成业务逻辑。
public static IDBQuery createJavassistBytecodeDynamicProxy() throws Exception {  
        ClassPool mPool = new ClassPool(true);  
        CtClass mCtc = mPool.makeClass(IDBQuery.class.getName() + "JavaassistBytecodeProxy"); //定义类名
        mCtc.addInterface(mPool.get(IDBQuery.class.getName()));  //添加业务接口
        mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc));  //添加构造函数
        mCtc.addField(CtField.make("public " + IDBQuery.class.getName() + " real;", mCtc)); //添加类的字段信息,使用动态java代码
        String dbqueryname=DBQuery.class.getName();
        //添加方法,使用动态代码指定内部逻辑
        mCtc.addMethod(CtNewMethod.make("public String request() { if(real==null)real=new "+dbqueryname+"();return real.request(); }", mCtc));  
        Class pc = mCtc.toClass();  //生成动态类
        IDBQuery bytecodeProxy = (IDBQuery) pc.newInstance(); //获取动态类实例
        return bytecodeProxy;  
    } 

客户端:
public class PerformanceMain {
    
    public static void main(String[] args) throws Exception {
        IDBQuery d=null;
        d = createJdkProxy();
        //d = cglibProxy();
    }

    //创建JDK代理类
    public static IDBQuery createJdkProxy(){
        IDBQuery jdkProxy = (IDBQuery) Proxy.newProxyInstance(
                ClassLoader.getSystemClassLoader(),  
                new Class[] { IDBQuery.class }, 
                new JdkDbQeuryHandler() //指定Handler
                );  
            return jdkProxy;  
    }

    //创建CGLIB代理类
    public static IDBQuery createCglibProxy(){
        Enhancer enhancer = new Enhancer();  
        enhancer.setCallback(new CglibDbQueryInterceptor());     //指定切入器
        enhancer.setInterfaces(new Class[] { IDBQuery.class });  //指定实现接口
        IDBQuery cglibProxy = (IDBQuery) enhancer.create();      //生成代理类的实例
        return cglibProxy;  
    }

    public static IDBQuery createJavassistDynProxy()  throws Exception {
        ProxyFactory proxyFactory = new ProxyFactory();  
        proxyFactory.setInterfaces(new Class[] { IDBQuery.class });  
        Class proxyClass = proxyFactory.createClass();  
        IDBQuery javassistProxy = (IDBQuery) proxyClass.newInstance();  
        ((ProxyObject) javassistProxy).setHandler(new JavassistDynDbQueryHandler());  
        return javassistProxy;  
    }
    
    public static IDBQuery createJavassistBytecodeDynamicProxy() throws Exception {  
        ClassPool mPool = new ClassPool(true);  
        CtClass mCtc = mPool.makeClass(IDBQuery.class.getName() + "JavaassistBytecodeProxy");
        mCtc.addInterface(mPool.get(IDBQuery.class.getName()));  
        mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc));  
        mCtc.addField(CtField.make("public " + IDBQuery.class.getName() + " real;", mCtc)); 
        String dbqueryname=DBQuery.class.getName();
        mCtc.addMethod(CtNewMethod.make("public String request() { if(real==null)real=new "+dbqueryname+"();return real.request(); }", mCtc));  
        Class pc = mCtc.toClass();  
        IDBQuery bytecodeProxy = (IDBQuery) pc.newInstance();
        return bytecodeProxy;  
    } 
}

与静态代理相比,动态代理可以很大幅度地减少代码行数,并提升系统的灵活性。在Java中,动态代理类的生成主要涉及对ClassLoader的使用。

以CGLIB为例,简要阐述动态代理的加载过程:

使用CGLIB生成动态代理,首先需要生成Enhancer类实例,并指定用于处理代理业务的回调类。在Enhancer.create()方法中,会使用DefaultGeneratorStrategy.Generate()方法生成动态代理类的字节码,并保存在byte数组中。接着使用ReflectUtils.defineClass()方法,通过反射,调用ClassLoader.defineClass()方法,将字节码装载到ClassLoader中,完成类的加载。最后使用ReflectUtils.newInstance()方法,通过反射,生成动态类的实例,并返回该实例。

实验三种代理方式,总结如下:
JDK的动态类创建过程最快,这是因为在这个内置实现中defineClass()方法被定义为native实现,故性能高于其他几种实现。但在代理类的函数调用性能上,JDK的动态代理就不如CGLIB和Javassist的基于动态代码的代理,而Javassist的基于代理工厂的代理实现,代理的性能质量最差,甚至不如JDK的实现。就动态代理的方法调用性能而言,CGLIB和Javassist的基于动态代码的代理都优于JDK自带的动态代理。此外,JDK的动态代理要求代理类和真实主题都实现同一个接口,而CGLIB和Javassist没有强制要求。

Hibernate中代理模式的应用:

Hiberante中的延迟加载主要有两种:一是属性的延迟加载,二是关联表的延时加载。同过Hibernate加载一条实体信息。此时,session载入的类并不是我们所定义的实体T;而是他的一个由CGLIB的Enhancer类生成的动态类,该类的父类才是我们定义的实体T;此外代理类还实现了它实现了HibernateProxy接口。由此可见,Hibernate使用一个动态代理子类替代用户定义的类。这样,在载入对象时,就不必初始化对象的所有信息,通过代理,拦截原有的getter方法,可以在真正使用对象数据时,才去数据库加载实际的数据,从而提升系统性能。

T u=(T)HibernateSessionFactory.getSession().load(T.class, 1);//加载用户id为1的实体

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值