• 常见的设计模式:单例模式


    首先要明确一个概念,什么是设计模式?设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式往往代表要解决某方面问题的最佳实现,通常被有经验的面向对象的软件开发人员所采用。 

    那么什么是单例模式呢?单例模式的定义是:一个类,在全局当中,有且仅含有一个实例,并且一般是由自身去进行实例化,再向外提供一个统一的获取方法,外界只能获取到该实例,但无法对实例进行修改,仅能对其进行读取或者应用,外界在调用这个类的时候,都是调用了同一个实例,最常用的场景就是多程序读取一个配置文件时,建议配置文件封装成对象,并且使用单例模式,会方便操作其中的数据,又要保证多个程序读到的是同一个配置文件对象,就需要该配置文件对象在内存中是唯一的。简单说来,单例模式(也叫单件模式)的作用就是保证在整个应用程序的生命周期中,任何一个时刻,单例类的实例都最多只存在一个(当然也可以不存在)。

    基于以上描述,那么在设计单例模式的情况下,我们应该要让设计的当前类满足一下条件,才能被称为单例模式:

    1.其他类和程序无法实例化当前类,所以可以知道当前类的构造函数一定是用private去进行修饰的(不可不写构造函数,因为默认的构造函数是public的)

    2.当前类要可以在类中实例化唯一一个自身对象,所以这个实例可以是静态static的(根据实例化的时机不同,可以分为饿汉模式和懒汉模式,之后有说)

    3.当前类要可以向外界提供一个统一的接口,提供实例,向外界提供一个getInstance方法,并且返回一个自身的唯一实例,因为其他的类无法实例化该类,所以该方法也必须是静态Static的(要么其他的类是无法获取到该类的实例的)。

    基于上述思路,可以完成如下单例模式的代码:

    package wellhold.bjtu.singleton;
    
    //饿汉模式
    public class Single {
    
        private static Single single=new Single();
        
        private Single()
        {
    
        }
        public static Single getInstance()
        {
            return single;
        }
    
    }

    在代码和前文当中,提到了两个名词,那就是饿汉模式和懒汉模式,所谓的饿汉模式,就是在程序启动的时候,类在加载的时候,该单例类的实例就已经是存在的模式,即从代码当中看,在Single类在被加载的时候,就已经实例化一个实例,叫single,并且静态的存储在了内存当中,等待其他的类或程序块调用,这种模式就是饿汉模式。饿汉模式毋庸置疑是线程安全的,因为该实例是在类加载的时候就已经存在内存当中,并且其他的类仅能调用它,不能修改它。

    而相对来说,懒汉模式则与饿汉模式相反,懒汉模式下,单例类在被加载的时候,实例还不存在,仅在其他对象在调用单例类提供的获取实例方式的时候,单例类才去创建这个唯一的实例(如果之前已经创建过,则不进行创建),之后再返回实例,这种模式,叫做懒汉模式。可以实现代码如下:

    package wellhold.bjtu.singleton;
    
    //懒汉模式
    public class Single {
    
        private static Single single;
        
        private Single()
        {
    
        }
        public static Single getInstance()
        {
            if(single==null)
                single=new Single();
            return single;
        }
    
    }

    可以从代码当中看出,懒汉模式这种情况下,是线程不安全的,具体可以见图(图来自:http://www.cnblogs.com/ysw-go/p/5386161.html)

    两个线程,线程一和线程二同时调用了getInstance方法,当线程1执行了if判断,single为空,还没来得及执行single =new Single()创建对象,这个时候线程2就来了,它也进行if判断,single依然为空,则创建Single对象,此时,两个线程就会创建两个对象,违背我们单例模式的初衷。那么要解决这个线程安全的问题,可以使用以下三种方法:

    1.加同步锁:

    package wellhold.bjtu.singleton;
    
    //懒汉模式-线程安全模式一
    public class Single {
    
        private static Single single;
        
        private Single()
        {
    
        }
        public synchronized static Single getInstance()
        {
            if(single==null)
                single=new Single();
            return single;
        }
    
    }

    但这种情况下,同步是需要开销的,而我们只需要在创建实例化的时候同步,所以又衍生出了第二种模式。

    2.双重检查锁定

    package wellhold.bjtu.singleton;
    
    //懒汉模式-线程安全模式二
    public class Single {
    
        private static Single single;
        
        private Single()
        {
    
        }
        public static Single getInstance()
        {
            if(single==null)
                synchronized(Single.class)
                {
                    if(single==null)
                        single=new Single();
                }
            return single;
        }
    
    }

    这样一种设计可以保证只产生一个实例,并且只会在初始化的时候加同步锁,看似精妙绝伦,但却会引发另一个问题,这个问题由指令重排序引起。

    指令重排序是为了优化指令,提高程序运行效率。指令重排序包括编译器重排序和运行时重排序。JVM规范规定,指令重排序可以在不影响单线程程序执行结果前提下进行。

    例如 instance = new Singleton() 可分解为如下伪代码:

     
    1. memory = allocate();   //1:分配对象的内存空间  
    2. ctorInstance(memory);  //2:初始化对象  
    3. instance = memory;     //3:设置instance指向刚分配的内存地址  

    但是经过重排序后如下:

     
    1. memory = allocate();   //1:分配对象的内存空间  
    2. instance = memory;     //3:设置instance指向刚分配的内存地址                       
    3.  //注意,此时对象还没有被初始化!  
    4. ctorInstance(memory);  //2:初始化对象  

    将第2步和第3步调换顺序,在单线程情况下不会影响程序执行的结果,但是在多线程情况下就不一样了。线程A执行了instance = memory(这对另一个线程B来说是可见的),此时线程B执行外层 if (instance == null),发现instance不为空,随即返回,但是得到的却是未被完全初始化的实例,在使用的时候必定会有风险,这正是双重检查锁定的问题所在! 

    这个问题在 J2SE 5.0 中已经被修复,可以使用 volatile 关键字来保证多线程下的单例

    //懒汉模式-线程安全模式二完整版
    public class Single {
    
        private static volatile Single single;
        
        private Single()
        {
    
        }
        public static Single getInstance()
        {
            if(single==null)
                synchronized(Single.class)
                {
                    if(single==null)
                        single=new Single();
                }
            return single;
        }
    
    }

    3.静态内部类方式实现懒汉模式的单例模式

    package wellhold.bjtu.singleton;
    
    //懒汉模式-线程安全模式三
    public class Single {
    
        private static class LazyHolder{
            
            private static final Single single=new Single();
        }
        
        private Single()
        {
    
        }
        public static Single getInstance()
        {
            return LazyHolder.single;
        }
    }

    这种模式是懒汉模式当中三中模式当中的最佳,即保证了实例化延迟,也保证了不浪费性能。

  • 相关阅读:
    【BUG】java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more than one time zone
    IntelliJ IDEA控制台输出中文乱码问题
    CMD命令
    MongoDB学习笔记
    MyBatis生成序列ID
    MongoDB配置问题
    正确处理下载文件时HTTP头的编码问题(Content-Disposition)
    SpringJPA主键生成采用自定义ID,自定义ID采用年月日时间格式
    Java根据经纬度算出附近正方形的四个角的经纬度
    gradle
  • 原文地址:https://www.cnblogs.com/WellHold/p/6634872.html
Copyright © 2020-2023  润新知