zoukankan      html  css  js  c++  java
  • 设计模式之单例模式

    0. 单例模式准备

        0.1 单例模式的作用

           0.1.1 一个类只有一个实例

           0.1.2 该实例能够通过一个全局的方法获取到

        0.2 单例模式适用场景:

          0.2.1 多线程之间操作一个对象【共享一个资源】

          0.2.2 性能优化,减少创建对象的消耗【创建时间、对象所占的空间】

       0.3 单例模式的特点

           0.3.1 具有私有的构造器一个静态变量一个静态方法、[一个静态内部类]

    1 饿汉模式

          1.1本质: 在类加载的时候就创建实例,需要获取实例时直接返回已创建的实例

          1.2 优点:线程安全

          1.3 缺点: 类加载的时候就创建实例,浪费空间

          1.4 代码示例:

    package com.blueStarWei.pattern;
    
    public class HungrySingleton {
    
        //create instance when loading class
        private static HungrySingleton singleton = new HungrySingleton();

    //将构造器私有化,防止外界通过该构造器创造多个实例 private HungrySingleton(){}

    public static HungrySingleton getInstance(){ return singleton; } }

    【为了减省空间的浪费,因此人们想到了在需要获取实例的时候创建实例,因此产生了懒汉模式】

    2. 懒汉模式

      2.1 本质: 当需要获取实例的时候,先进行判断,如果实例不存在,就创建实例,否则直接返回已存在的实例【避免了在加载类的同时直接创建实例】

      2.2 懒汉模式1.0版本

    package com.blueStarWei.pattern;
    
    public class LazySingleton {
    
        private static LazySingleton singleton;

        private LazySingleton(){}
    public static LazySingleton getInstance(){ if(singleton == null){ singleton = new LazySingleton(); } return singleton; } }

        2.2.1 优点:节省了空间

        2.2.2 缺点: 线程不安全【如果多个线程同时执行if判断,因为多个线程获取的singleton1都是空,所以会创建多个实例】

    【为了避免懒汉模式的线程不安全问题,人们想到了对getInstance()添加synchronized修饰符,于是产生了懒汉模式1.1版本】

      2.3 懒汉模式1.1版本

    package com.blueStarWei.pattern;
    
    public class LazySingleton1 {
    
        private static LazySingleton1 singleton;

    private LazySingleton1(){}
    public static synchronized LazySingleton1 getInstance(){ if(singleton == null){ singleton = new LazySingleton1(); } return singleton; } }

        2.3.1 优点: 节省空间、线程安全

        2.3.2 缺点: 性能不好【synchronized会造成线程阻塞】

    【为了避免线程阻塞的出现,人们又想到了双重校验锁,因此产生了懒汉模式1.2版本】

      2.4 懒汉模式1.2版本【双重检查枷锁方式/double-check】

    package com.blueStarWei.pattern;
    
    public class LazySingleton2 {
    
    //使用volatile阻止命令重排,保证多线程正确处理singleton变量
    private volatile static LazySingleton2 singleton;

    private LazySingleton2(){}
    public static LazySingleton2 getInstance(){ if(singleton == null){
    //该部分只会被执行一次
    synchronized (LazySingleton1.class) { if(singleton == null){ singleton = new LazySingleton2(); } } } return singleton; } }

        2.4.1 优点: 节省空间,线程安全,性能相对有优化【只是优化了已经存在实例的情况,即:当实例已经被创建,再调用getInstance()的时候就不会经过synchronized修饰符】

        2.4.2 缺点:使用了synchronized修饰符,线程仍然会有阻塞情况的发生。

    【为了避免使用synchronized修饰符,人们想到了静态内部类(因为一个类被加载,当且仅当该类的某个静态成员被调用),于是懒汉模式2.0时代到来】

      2.5 懒汉模式2.0版本【推荐版本

    package com.blueStarWei.pattern;
    /**
     * 因为对象实例化是在内部类加载时构建,因此是线程安全的【毕竟类只加载一次嘛】
     * @author HuWei
     *
     */
    public class LazySingleton3 {

    private LazySingleton3(){}
    //当getInstance()方法第一次被调用时,内部类被加载 static class SingletonHolder { private static final LazySingleton3 singleton3 = new LazySingleton3(); } //没有使用synchronized修饰符,避免了性能损耗 public static LazySingleton3 getInstance() { return SingletonHolder.singleton3; } }

         2.5.1 优点: 节省空间、线程安全、避免性能的额外损耗

         2.5.2 缺点: 在反射作用下,单例模式会被破坏

    【为了防止反射破坏单例模式,所以产生了单例模式3.0版本】

      2.6 单例模式3.0版本

         2.6.1 版本代码

    package com.blueStarWei.pattern;
    
    public class LazySingleton4 {
        
        /* -------添加部分 start-------- 
         * 作用:第二次调用构造函数(即:创建第二个实例)时抛出异常
         * */
        private static boolean initialized = false;
        
        private LazySingleton4() {
            synchronized (LazySingleton4.class) {
                if(initialized == false){
                    initialized = !initialized;
                }else{
                    throw new RuntimeException("Singleton has been broken!");
                }
            }
        }
        /* -------添加部分 end-------- */
        
        //当getInstance()方法第一次被调用时,内部类被加载
            static class SingletonHolder {
                private static final LazySingleton4 singleton4 = new LazySingleton4();
            }
            
            //没有使用synchronized修饰符,避免了性能损耗
            public static LazySingleton4 getInstance() {
                return SingletonHolder.singleton4;
            }
    }

            2.6.1.1  版本Demo

    package com.blueStarWei.pattern;
    
    import java.lang.reflect.Constructor;
    /**
     * 通过反射调用时会报错
     * Caused by: java.lang.RuntimeException: Singleton has been broken!
     * @author HuWei
     *
     */
    public class LazySingleton4Demo {
    
        public static void main(String[] args) {
            //通过getInstance()创建实例
            LazySingleton4 sington = LazySingleton4.getInstance();
            
            //通过反射创建实例
            LazySingleton4 sington4 = null;
            try {
                Class<LazySingleton4> clazz = LazySingleton4.class;
    Constructor
    <LazySingleton4> constructor = clazz.getDeclaredConstructor();
    constructor.setAccessible(true); sington4
    = constructor.newInstance(); } catch (Exception e) { e.printStackTrace(); } System.out.println("getInstance() : "+sington); System.out.println("invoke : "+sington4); } }

             2.6.1.2 Demo日志

    java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
        at java.lang.reflect.Constructor.newInstance(Unknown Source)
        at com.blueStarWei.pattern.LazySingleton4Demo.main(LazySingleton4Demo.java:21)
    Caused by: java.lang.RuntimeException: Singleton has been broken!
        at com.blueStarWei.pattern.LazySingleton4.<init>(LazySingleton4.java:19)
        ... 5 more
    getInstance() : com.blueStarWei.pattern.LazySingleton4@7852e922
    getInstance() : com.blueStarWei.pattern.LazySingleton4@7852e922
    invoke : null

        2.6.2 反射破环单例模式示例

    package com.blueStarWei.pattern;
    
    import java.lang.reflect.Constructor;
    /**
     * 不难发现,通过getInstance()和反射创建的实例的内存地址不一样,即不是同一个实例对象
     * @author HuWei
     *
     */
    public class LazySingleton3Demo {
    
        public static void main(String[] args) {
            //通过getInstance()创建实例
            LazySingleton3 sington = LazySingleton3.getInstance();
            LazySingleton3 sington2 = LazySingleton3.getInstance();
            
            //通过反射创建实例
            LazySingleton3 sington3 = null;
            try {
                Class<LazySingleton3> clazz = LazySingleton3.class;
                Constructor<LazySingleton3> constructor = clazz.getDeclaredConstructor();
    constructor.setAccessible(true); sington3
    = constructor.newInstance(); } catch (Exception e) { e.printStackTrace(); } System.out.println("getInstance() : "+sington); //getInstance() : com.blueStarWei.pattern.LazySingleton3@15db9742 System.out.println("getInstance() : "+sington2); //getInstance() : com.blueStarWei.pattern.LazySingleton3@15db9742 System.out.println("invoke : "+sington3); //invoke : com.blueStarWei.pattern.LazySingleton3@6d06d69c } }

     【在分布式系统中,单例类有时需要实现Serializable接口。以上版本通过序列和反序列化产生的实例将会不是同个实例对象,所以产生了懒汉模式4.0版本】

      2.7 懒汉模式4.0版本

        2.7.1 Demo代码

    package com.blueStarWei.pattern;
    
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.ObjectInput;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutput;
    import java.io.ObjectOutputStream;
    
    public class LazySingleton4Demo2 {
    
        public static void main(String[] args) {
            
            LazySingleton4 singleton = LazySingleton4.getInstance();
            try {
                ObjectOutput out = new ObjectOutputStream(new FileOutputStream("fileName.txt"));
                out.writeObject(singleton);
                out.close();
                //deserializable from file to object
                ObjectInput in = new ObjectInputStream(new FileInputStream("fileName.txt"));
                LazySingleton4 singleton2 = (LazySingleton4)in.readObject();
                in.close();
                
                System.out.println("Serializable : "+singleton);
                            
                System.out.println("Deserializable : "+singleton2);
                
            } catch (Exception e) {
                e.printStackTrace();
            } 
            
        }
    }

      2.7.2 反序列返回不同的对象示例

    public class LazySingleton4 implements Serializable

            2.7.2.1 Demo日志

    Serializable : com.blueStarWei.pattern.LazySingleton4@33909752
    Deserializable : com.blueStarWei.pattern.LazySingleton4@776ec8df

      2.7.3 版本代码

    package com.blueStarWei.pattern;
    
    import java.io.Serializable;
    
    public class LazySingleton4 implements Serializable{
        
        private static final long serialVersionUID = 1L;
        
        private static boolean initialized = false;

    private LazySingleton4() { synchronized (LazySingleton4.class) { if(initialized == false){ initialized = !initialized; }else{ throw new RuntimeException("Singleton has been broken!"); } } } static class SingletonHolder { private static final LazySingleton4 singleton4 = new LazySingleton4(); } public static LazySingleton4 getInstance() { return SingletonHolder.singleton4; } /* ----添加部分 start---- */ /** * 反序列化的结果仍然通过getInstance()获取 */ private Object readResolve(){ return getInstance(); } /* ----添加部分 end---- */ }

            2.7.3.1 Demo日志

    Serializable : com.blueStarWei.pattern.LazySingleton4@33909752
    Deserializable : com.blueStarWei.pattern.LazySingleton4@33909752

    3 其它问题

      3.1 如果存在多个类加载器,可能会产生多个单例并存的现象。

           3.1.1 原因: 每个类加载器都定义了一个命名空间,如果存在多个类加载器,不同的类加载器可能会加载同一个类,所以同一类会被加载多次。

           3.1.2 解决方法: 自行指定类加载器,并指定同一个类加载加载器。

      3.2 单例类不能被继承

          3.2.1 原因: 继承单例类必须将单例类构造器的访问修饰符改成public/protected,这样会导致其他类也可以实例化该单例类,破坏了单例模式

          3.2.2 解决方法: 不继承单例类

      3.3 为何全局变量会比单例模式差?

         3.3.1 全局变量基本上就是对对象的静态引用,全局变量虽然能提供全局访问,但是不能保证只有一个实例

    4 总结

       实际项目中,一般从懒汉模式2.0、3.0、4.0版本中选择一种即可

             更多内容,请访问:http://www.cnblogs.com/BlueStarWei/

  • 相关阅读:
    C#实现office文档转换为PDF或xps的一些方法
    Asp.Net性能优化.
    我们该如何在网上寻找资源!!!!
    用谷歌浏览器来当手机模拟器
    js 文档高度
    文件的ContentType类型网页设计,HTML/CSS
    javascript 获取滚动条高度+常用js页面宽度与高度[转]
    学习MVC
    windows自带性能监控器
    C#语言规范之小结
  • 原文地址:https://www.cnblogs.com/BlueStarWei/p/8997932.html
Copyright © 2011-2022 走看看