• 设计模式之单例模式(包含三种方式)


      设计模式是一套被反复使用,多数人知晓,经过分类编目的,代码设计的总结,也可以说是前人的智慧结晶。学习设计模式能让我们对一些应用场景使用相同的套路达到很好的效果,我会不定时更新一些自己对设计模式的理解的文章,从定义,实现,应用场景来说说设计模式,今天我要说的对象是单例模式
    一,定义
      什么是单例模式,字面理解,这种设计模式的目的是在一定程度上保证对象在程序中只有一个,或者只被创建一次,之所以说在一定程度上是因为还有还有反射机制存在,如果不考虑反射的话,确实是可以保证对象唯一性的,这里我们不考虑反射。
    二,实现
      那么如何保证对象的唯一性呢,可以从两方面出发,第一,对象的创建,第二,对象的获取。如果我们只给目标类提供一个构造方法,并且把这个构造方法私有化(private),那么就可以保证对象无法被创建了(注意,一定要私有化,有的同学会想,我不写构造方法是不是就可以了,注意不写是会报错的,因为所有的类都必须从Object这个父类中继承无参构造方法,有的同学又会说,我没写也没报错啊,那是因为你的开发工具,或者工具框架默认给你创建了!),当然对象绝对的无法创建是没有意义的,对象都没发创建了,那这个对象存在也不能发挥作用,所以我们对外提供一个唯一的获取对象的方法,由于不能创建对象,所以这个方法必须是静态的(static),然后保证获取的这个对象是唯一的,就可以达到我们的目的。那么,如果保证我们提供的对象是唯一的呢,从实现方式来说,可以把设计模式分为3类,分别是饿汉式,懒汉式,登记式
      1.饿汉式
        把对象理解为面包,一个饿汉对面包的态度是怎样的,肯定是希望自己马上就拥有面包,饿汉式的原理也是如此,在程序初始化的时候就创建对象。下面展示饿汉式的创建代码
        public class SingleCase{

          private static SingleCase singleCase=initCase();

          private SingleCase(){}//私有化构造方法

          private static SingleCase initCase(){
            //这里就可以写具体的对象初始化信息了
            //由于这里是演示代码,我就简单的new一下
            return new SingleCase();
          }
          public static SingleCase getInstance(){

            return singleCase;

          }
        }
      2.懒汉式
        同样把对象理解为面包,一个懒汉的态度就不一样了,因为很懒,在他不饿,或者说不需要的时候,他是不会去拿面吧的(创建对象),所以懒汉式的核心思想是:需要的时候再创建对象。下面演示懒汉式单例创建代码,为了让看客们更容易看懂,我们在饿汉式的基础上做修改
        public class SingleCase{

          private static SingleCase singleCase=null;//这里没有调用创建对象的方法,所以初始化的时候的singleCase值是空的

          private SingleCase(){}

          private static SingleCase initCase(){

          return new SingleCase();

          }
          public static SingleCase getInstance(){

            //相比于饿汉式,这里多了一个判断,如果singleCase是空的,就创建,如果singleCase不是空的,那就直接返回
              if (singleCase==null){

                return initCase();

              } 
            return singleCase;
          }
        }
        以上代码就是懒汉式单例模式的实现方式,但是细心的看客可能发现了一个问题,就getInstance()方法如果被多线程访问的话,可能导致创建多个对象。我们知道,java是一门多线程语言,cpu的执行也并不是连续的,假设一台计算机同时有10个进程在运行,每个进程3个线程,那总共就有30个线程,cpu的工作就是在这30个线程之间快速的切换,执行一下1号线程,再执行一下2号线程,再执行一下8号线程,再执行一下18号线程,再执行一下一号线程,这种切换机制是随机的,在一秒钟之内,可能有的线程被执行100次,也有的线程可能被执行10次。那么再上面的判断中,可能会出现某个线程刚执行判断if (singleCase==null)还没来得及执行initCase()方法cpu就切换到另外一个线程,而这个线程也在执行if (singleCase==null)判断,而这时候对象还未被创建,所以也会进入if后面的代码块,最终导致initCase()被执行两次,也就是创建了2个对象,这显然和我们的初衷不和。为了保证对象的性,让方法只被执行一次,我们这里使用synchronized关键字,synchronized作用在静态方法上,可以保证在整个程序中方法只能同时被一个线程执行,这个线程在执行的时候会拥有这个方法的锁,导致其他线程无法获取锁,也就无法执行,当该线程执行完了之后,会释放这个锁,然后才能被其他线程执行(关于synchronized关键字,如果有看客不清楚用法可以在评论区留言,如果人数多的话我再开个单章讲讲这个关键字),下面展示加synchronized之后的代码,只需要在原有的方法上直接加上去即可
        public class SingleCase{

          private static SingleCase singleCase=null;

          private SingleCase(){}

          private static SingleCase initCase(){

            return new SingleCase();

          }
          //直接加载方法上
          public synchronized static SingleCase getInstance(){

            if (singleCase==null)

                return initCase();

            return singleCase;
            }
        }
      以上写法确实可以解决高并发的时候创建出多个对象的现象,但是并不完美,因为加了synchronized之后,每次调用方法都会加锁,造成阻塞,影响性能,而我们其实仅仅只需要保证在singleCase==null创建对象的时候上锁就可以了,而大部分情况是对象已经创建好了,直接获取的,我们不希望影响这种情况的性能。那么,我们可以把锁放在确定对象没有被创建之后,代码更改如下
        class SingleCase{

          private static SingleCase singleCase=null;

          private SingleCase(){}

          private static SingleCase initCase(){

            return new SingleCase();

          }
          public static SingleCase getInstance(){

            if (singleCase==null){

              synchronized (SingleCase.class){//synchronized的用法还请各看客自行学习,或者评论区留言

                if (singleCase==null){

                  singleCase=initCase();

                }
              }
            }
            return singleCase;
          }
        }
        关于synchronized (SingleCase.class){}里面的代码块又加了一个if (singleCase==null)的判断,可能有看客会奇怪,为什么这里还要加一次判断,其实这里跟上面说的一样,第一次singleCase==null的判断可能出现在多个线程中,导致多个线程进入判断后面的代码块,虽然不能进入被synchronized锁定的代码块,但是只要有线程进入了第一次singleCase==null判断之后的代码块,当上一个拥有synchronized锁的线程执行完创建对象的代码释放锁之后,当前线程会继续执行synchronized锁后面的代码再次创建对象,而这里再加一个判断,就可以解决这个问题
      3.登记式
        还是把对象理解为面包,不同于懒汉式和饿汉式的单个面包,登记式也可以说是厨师式,因为它操作的是多种面包,就像厨师一样,他拥有很多面包。那么多个面包怎么存放呢?面包柜Map!就像我们在店里看到的面包柜一样,每种面包都有对应的名字,就是我们这里的key,一个名字对应一个面包,一个key对应一个对象。为了让这个key被所有人熟知,登记式的可以一般使用class.getName();我们知道单例模式的第一设计原则就是私有化构造方法,想要在一个类中存储多个对象,又不能通过new的方法,那只有一条路可以走了,反射。登记式就是利用反射来创建对象,到这里肯定有看客会说,既然用到反射了,那么不通过你的面包柜,我也可以拿到面包吧,的确如此,这也是我个人比较诟病登记式的一个地方。不过登记式在特殊的场景中能发挥极其强大的作用,比如Spring的bean容器!Spring的bean容器不完全是登记式,不过实现方式上有很多相通的地方。登记式相比于懒汉式有更丰富的创建方法,也涉及线程安全问题,这里不一一演示,请各位看客自行实验,下面展示登记式单例的简单创建方法
          public class SingleCase2 {

            private static Map singletonMap = new HashMap();

            private SingleCase2() {}

            public static Object getInstance(String className) throws Exception{

              if (!singletonMap.containsKey(className)) {

                singletonMap.put(className, Class.forName(className).newInstance());

              }
              return singletonMap.get(className);
            }
          }
    三.应用场景
      单例模式的应用场景在网上随便搜索一下能出来几百篇文章,大多长篇大论晦涩难懂,我喜欢用一些简单的话说明一些简单的道理。我们在说什么时候用单例时不如先来想想单例能实现的效果,首先单例能现实的效果是能保证程序中目标类的对象只有一个,那么什么时候需要保证类的对象只有一个呢,比如,某个配置文件类的的对象,我们肯定希望每次访问的时候配置信息是一致的,如果文件信息有更新的话,直接就能从对应的类的对象中读取到,如果这个类的对象有多个,可能会造成信息不一致,或者更新不及时。另外,保存一些公共资源时,比如java的线程池,我们希望对某个线程的数量有一个准确的限制,如果不适用单例,控制起来是不是就会非常麻烦。然后,单例的另一个优点,减少资源消耗,当对象需要被大量访问的时候,是每次创建一个对象呢,还是重复适用同一个对象,当然是使用同一个对象占用的资源少,Spring就是这么干的。那么又什么时候使用懒汉式,什么时候使用饿汉式呢,如果你的对象再 程序启动之后马上就要使用的,需要初始化时就创建对象,那么毫无疑问选择饿汉式,如果你的对象指不定什么时候用,又不或者可能根本用不到,那么使用懒汉式可能比较经济一点。至于登记模式,但从使用的时间来说,可以根据需要设计成懒汉式或者饿汉式,例子中是懒汉式。当然,登记模式一般在复杂场景中发挥重要的作用,比如Spring,而我们实际开发中运用较少。
      ps:今天就说这么多,如果你喜欢,请帮忙点赞,评论,转发。你的的肯定是我写下去的动力

  • 相关阅读:
    都不敢上CSDN了
    什么是函数(function)?
    今天3/8妇女节
    一件有意思的事情:关于std::string和std::auto_ptr
    转两篇Link相关的文章
    DevIL Downloads
    状态模式(State Pattern)
    访问者模式(Visitor Pattern)
    羊皮卷的故事第二章
    备忘录模式(Memento Pattern)
  • 原文地址:https://www.cnblogs.com/farmer-lau/p/10045465.html
Copyright © 2020-2023  润新知