• 读书笔记-设计模式


    单例模式的并发问题和延迟加载

    非并发的单例模式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    public class Singleton {  
    
        private static Singleton instance = null; 
    
        private Singleton() { 
    
        } 
    
        public static Singleton getInstance() { 
    
            if (instance == null) {     
    
                  instance = new Singleton(); 
    
            } 
    
            return instance; 
    
        } 
    } 
    

    多线程下,上面的instance == null 与 instance = new Singleton()的操作不是原子的,所以会导致instance == null的判断不正确,从而导致出现多个实例;

    synchronized能保证上述的原子性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    public class Singleton {  
    
        private static Singleton instance = null; 
    
        private Singleton () { 
    
        } 
    
        public static synchronized Singleton getInstance() { 
    
            if (instance == null) { 
    
                     instance = new SynchronizedSingleton(); 
    
            } 
    
           return instance; 
    
         } 
    } 
    

    synchronized解决了原子性问题,但是,当第一次Singleton实例构造之后,这个instance对所有线程都可见了,所有线程都能正确判断instance == null为false了,此时再synchronized就没必要了,造成性能开销;

    将Singleton直接new一个静态属性实例能解决这个问题:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    public class Singleton { 
    
        private static Singleton instance = new Singleton (); 
    
        private Singleton () { 
    
        } 
    
        public static Singleton getInstance() { 
    
             return instance; 
    
         } 
     } 
    

    这样,在类加载的时候,实例就已经被new出来了;虽然解决了并发问题,但是,实例化太早,如果没有代码要用这个单例对象,这个加载就是没必要的;

    但是,假如这个单例类本身没其他的方法和角色,那么它也不会被JVM加载,除非真需要一个单例实例的时候;

    Anyway,单例类的构造一般是个漫长的过程,并且单例类一般还会充当其他角色拥有其他方法,所以过早加载是不可取的;

    使用static过早加载的消耗远低于使用synchronized造成的消耗;

    有既能够解决过早加载,又能解决同步性能消耗的方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    public class Singleton { 
    
        private Singleton () { 
    
        } 
    
        private static class SingletonHolder {
    
                private static Singleton instance = new Singleton (); 
    
        }
    
        public static Singleton getInstance() { 
    
             return SingletonHolder.instance; 
         } 
     } 
    

    当Singleton被加载时,其内部类SingletonHolder并不会被初始化,解决了过早加载;

    而new是在类加载时完成的,天生就不存在多线程的问题;

    PS: 静态的代码是不需要同步的,因为在类加载时,不存在多线程的问题。

    并没有终极完美的单例模式,比如序列化和反序列化会破坏单例。


    代理模式和动态代理

    代理模式的应用场景:

    1, 屏蔽客户代码直接访问真实对象或者需要代理类处理额外的技术细节,就是将委托类组合到一个代理类中,让代理类表现出所有委托类的接口,其实代理类就是实现了跟委托类的同一个接口,这样就可以在通过代理类访问委托类的时候加上访问控制和额外处理;

    2,延迟加载委托类,也就是真实业务类。 代理类是个虚架子,所有的调用最终还是转换为真实业务类的调用;之所以可以用对代理类的调用替代对委托类的调用,是因为代理类和委托类必须实现同样的接口,如果委托类没抽象出接口来,那么就变成了简单的组合模式了;

    之所以能延迟加载,是说,当客户代码需要持有一个真实业务类的实例但不会马上使用时,我们就先给它一个同样接口的代理类,等真正要使用时,我们再初始化委托类成实例做具体操作,这种场景下,委托类的实例化一般是费时耗性能的;

    延迟加载的意义就是在时间轴上把系统的压力分散了,设想有很多个委托类,如果系统一次加载,必然耗时,但如果都代理之,哪个要使用再加载哪个,这就将集中的加载分散开了;

    下面只举例延迟加载的应用场景,但在方法中可以加访问控制和其他额外处理细节:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    
    interface IDBQuery {
    
        String query();    //费时操作
    
    }
    
    class DBQuery implements IDBQuery {
    
        public String query() {
    
            return "Query result";
    
        }
    
    }
    
    class DBQueryProxy implements IDBQuery {
    
        private DBQuery realDBQuery = null;
    
        public String query() {
    
            if(realDBQuery == null)
    
                realDBQuery = new DBQuery();
    
            //可加访问控制和其他额外处理
    
            return realDBQuery.query();
    
        }
    
    }
    
    {//不使用代理类
    
        //持有但不使用阶段,已经构造了实例,费时费空间
    
        IDBQuery query = new DBQuery(); 
    
        //query费时费空间
    
        //...
    
        //真正要使用
    
        query.query();
    
    }
    
    {//使用代理类
    
        //持有但不使用阶段,没有构造真实业务实例
    
        IDBQuery query = new DBQueryProxy();
    
        //系统无此方面的负担
    
        //...
    
        //真正要使用
    
        query.query();
    }
    

    动态代理:

    代理模式中描述的代理类,

    如果是源码编写,在编译时就生成了其字节码,那就是静态代理;

    如果运行期,由反射机制动态创建的字节码,就是动态代理,动态代理已经由JDK和其他的一些库封装好了,只需要调用即可;

    动态代理生成代理类字节码的步骤:

    1, 通过实现 InvocationHandler 接口创建自己的调用处理器;

    2, 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;

    3, 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;

    4, 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。

    java.lang.reflect.Proxy的静态方法 .newProxyInstance 封装了步骤 2 到步骤 4 的过程:

    1
    2
    3
    
    InvocationHandler handler = new InvocationHandlerImpl(..); 
    
    Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, new Class[] { Interface.class },  handler ); 
    

    实际上,InvocationHandler的内部实现,就是一个静态代理类中的代理类的实现,所以延迟加载、额外处理、访问控制也都是在InvocationHandler中做的;只不过InvocationHandler不用实现委托类的接口;

    衡量一个动态代理实现的性能:

    1, 看代理类被创建的速度,就是Proxy.newProxyInstance的返回速度;

    2, 看代理的方法的执行速度,因为代理方法不是直接被调用的,是通过InvocationHandler.invoke()转发的,所以就看这个invoke转发的速度如何;其他的库可能用别的类似于invoke的转发方法,这之间就会有差距和比较;


    观察者模式

    观察者模式的意义:

    在单线程中,实现一个对象及时得知自身所依赖的对象的状态变化;

    不然,则只能在一个单独的线程中轮询监听所依赖的对象的状态变化,这个既不能及时,又开销巨大;

    JDK内置了观察者模式API,可直接复用:

    java.util.Observable 是个class,实现了增加、删除、通知观察者Observer的方法:

    private Vector obs;

    public void notifyObservers(Object arg){…}

    public void notifyObservers() {…}

    public synchronized void deleteObserver(Observer o) {…}

    public synchronized void addObserver(Observer o){…}

    java.util.Observer 是个interface,定义了update方法:

    void update(Observable o, Object arg);

    JButton的事件处理就是一个观察者模式:

    AbstractButton就是被观察者,维护了观察者列表,内部的fireActionPerformed(ActionEvent event)就是通知观察者状态改变的方法;

    ActionListener就是观察者,actionPerformed(ActionEvent event)就是update方法;

    一般ActionListener就是一个Model类;

  • 相关阅读:
    2020.1.15考试总结
    P4558 [JSOI2018]机器人 结论&DP
    2020.1.11考试总结
    2020.1.9考试总结
    如何和出题人斗智斗勇?奇技淫巧汇总
    各种公式总结
    2020.1.5考试总结
    C基础学习笔记——01-C基础第10天(内存结构)
    C基础学习笔记——01-C基础第09天(指针下)
    C基础学习笔记——01-C基础第08天(指针上)
  • 原文地址:https://www.cnblogs.com/mosthink/p/5288853.html
Copyright © 2020-2023  润新知