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

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

      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:今天就说这么多,若是你喜欢,请帮忙点赞,评论,转发。你的的确定是我写下去的动力

相关文章
相关标签/搜索