• 设计模式之单例设计模式


    设计模式之单例设计模式

      单例模式的实现目标就是保证一个类有且仅有一个实例,当然这也是有前提的,就是由同一个ClassLoader加载的这个类有且仅有一个对象,如果这里类由不同的ClassLoader加载,则会产生多个对象。

      (一) 单线程下的单例设计模式

      (1)饿汉式  

    public class Singleton {
        private static final Singleton instance = new Singleton();
        //构造函数私有化
        private Singleton() {
            
        }
        public static Singleton getInstance() {
            return instance;
        }
    
    }

      (2)懒汉式

    public class Singleton {
        private static Singleton instance = null;
        //构造函数私有化
        private Singleton() {
            
        }
        public static Singleton getInstance() {
            if(null == instance) {
                instance = new Singleton();
            }
            return instance;
        }
    }

      (二)多线程下的单例设计模式

      在多线程环境下,饿汉式单例模式是线程安全的。但懒汉式单例模式getInstance()的if语句形成了一个if-then-act操作,它并不是一个原子操作,因此可能引发线程安全问题,创建多个对象,破坏单例性。

      很容易让我们想到的是,采用同步(synchronized)的方式加锁,保证线程安全,如下所示:

    /**
     * 基于简单加锁的单例模式实现
     * @author Administrator
     *
     */
    public class Singleton {
        private static Singleton instance = null;
        //构造函数私有化
        private Singleton() {
            
        }
        public static Singleton getInstance() {
            synchronized(Singleton.class) {
                if(instance == null) {
                    instance = new Singleton();
                }
            }
            return instance;
        }
    }

      这种方法的单例模式固然是线程安全的,但是每个线程在执行getInstance()方法时,都必须申请锁,增加了系统的开销,降低了效率,而我们需要的仅仅是第一次创建对象时加锁即可,之后所有线程可并发获得该对象。于是我们采用双重检查锁定的方式实现单例模式:

    /**
     * 基于双重检查锁定的单例模式实现(错误代码)
     * @author Administrator
     *
     */
    public class Singleton {
        private static Singleton instance = null;
        //构造函数私有化
        private Singleton() {
            
        }
        public static Singleton getInstance() {
            if(null == instance) {//第一次判断用于判断对象是否被已经初始化,当对象已经初始化时,直接返回对象,避免锁的申请
                synchronized(Singleton.class) {
                    if(instance == null) {//第二次判断用来避免创建多个对象
                        instance = new Singleton();//语句①
                    }
                }
            }
            return instance;
        }
    }

      看似这种做法很巧妙解决了多线程环境下的单例模式的安全问题。其实不然,上述代码中的语句①可分解为三个子操作,分配对象所需的内存空间(子操作①)、初始化对象(子操作②)和将对象引用传入栈内的变量。想象一下这样一个场景,线程t1通过了第一次null == instance的判断,线程t1持有锁进行了第二次判断,此时线程t1进入了临界区,根据临界区重排序规则:临界区内的代码运行被重排序,因此,子操作③可能排在了子操作②之前,因此当线程t1执行到操作③时,由于此时操作②并没有执行,因此instance对象并没有初始化完成,但是instance以不为空,就在此时,线程t2执行了第一次null==instance判断,随然instance并未初始化完成,但以不是 null,所以直接返回给线程t2 instance对象,这样线程t2调用instance对象时就可能产生未知的错误。所以上述代码是不正确的。

      需要将instance变量的采用volatile修饰,禁止其写操作与该操作之前的任何读、写操作进行重排序,因此,用volatile修饰instance相当于禁止了子操作②(对对象进行初始化的写操作)重拍到子操作③(对对象引用写入共享变量的子操作)之前,保证了线程读到的instance都是初始化完成的instance。

    /**
     * 基于双重检查锁定的单例模式实现(正确实现)
     * @author Administrator
     *
     */
    public class Singleton {
        private static volatile Singleton instance = null;//保障有序性
        //构造函数私有化
        private Singleton() {
            
        }
        public static Singleton getInstance() {
            if(null == instance) {//第一次判断用于判断对象是否被已经初始化,当对象已经初始化时,直接返回对象,避免锁的申请
                synchronized(Singleton.class) {
                    if(instance == null) {//第二次判断用来避免创建多个对象
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }

      考虑到双重检测锁定法实现上容易出错,我们选择另外一种可以延迟加载的单例模式:基于静态内部类的单例模式。

      静态内部类只有在初次访问时才会触发虚拟机对该类进行初始化,因此SingletonInstanceHolder.INSTANCE会初始化静态内部类 

    /**
     * 基于静态内部类的单例模式实现
     * @author Administrator
     *
     */
    public class Singleton {
        //构造函数私有化
        private Singleton() {
            
        }
        
        private static class SingletonInstanceHolder {
            private final static Singleton INSTANCE = new Singleton();
        }
        public static Singleton getInstance() {
            return SingletonInstanceHolder.INSTANCE;
        }
    }

      也可通过枚举来实现线程安全的单例模式:

    public enum Singleton {
        INSTANCE;//为单例对象
        
        Singleton() {
            
        }
        
        public void doSomething() {
            
        }
    }
  • 相关阅读:
    【linux】Centos下登陆mysql报错#1045
    tomcat在centos7里面启动很慢的解决办法
    tomcat日志文件 转载https://www.cnblogs.com/operationhome/p/9680040.html
    tomcat的文件目录结构
    centos 7服务器下tomcat 问题 1.配置问题
    x11转发遇到的问题
    x11转发,可以在shell里面看到图形界面
    linux里面tomcat配置遇到的问题
    vim中文乱码 vim字符集设置
    c#.net常见字符串处理方法
  • 原文地址:https://www.cnblogs.com/gdy1993/p/9157458.html
Copyright © 2020-2023  润新知