• 设计模式:单例模式


    设计模式:单例模式

    一、前言

      单例模式比较简单,可以说没有复杂的调用和接口的设计,就是一个简单的类,只是要求这个类只能生成一个对象,无论是什么时候都要保证这一点,因此只能生成一个实例的模式就叫做单例模式。这是一种新的思想,在前面我们学过了迭代器和适配器的适配思想;模板方法和工厂方法的交给子类思想;现在我们学习一下生成实例的思想。

    二、导引和代码

      2.1、什么时候类被加载和初始化?

       类的加载是通过类加载器(Classloader)完成的,它既可以是饿汉式加载类,也可以是懒汉式加载,这跟不同的JVM实现有关。加载完类后,类的初始化就会发生,如果是对一个类的主动使用就会初始化对象,对类的被动使用不会对类进行初始化,比如final修饰的静态变量如果能在编译时就确定变量的取值,会被当做常量,作为对一个类的被动使用不会导致类的初始化以下情况类被初始化

        最常见的就是直接创建一个类的实例(new 一个对象),有可能导致ClassNotFoundException。
        调用一个类的静态方法,public static XXX();
        调用一个类的静态变量并且它不是常量或者是对静态变量进行赋值;
        调用Java API的反射,例如动态加载一个类Class.forName();
        初始化一个类的子类会首先初始化一个类的父类;
        启动包含main()的类;
        在顶层类中执行assert语句;

      类初始化的一些规则:

        类从顶至底的顺序初始化,所以声明在顶部的字段的早于底部的字段初始化;
        超类早于子类和衍生类的初始化;
        如果类的初始化是由于访问静态域而触发,那么只有声明静态域的类才被初始化,而不会触发超类的初始化或者子类的初始化,即使静态域被子类或子接口或者它的实现类所引用;
        接口初始化不会导致父接口的初始化;
        静态域的初始化是在类的静态初始化期间,非静态域的初始化时在类的实例创建期间,这意味着静态域初始化在非静态域之前;
        非静态域通过构造器初始化,子类在做任何初始化之前构造器会隐含地调用父类的构造器,他保证了父类非静态或实例变量初始化早于子类;

      2.2、单例代码

    Singleton类:

     1 package zyr.dp.singleton;
     2 
     3 public class Singleton {
     4    private static Singleton singleton=new Singleton();
     5    private Singleton(){
     6        System.out.println("开始初始化对象...");
     7    }
     8    public static Singleton getInstance(){
     9        return singleton;
    10    }
    11 }

    Main函数:

     1 package zyr.dp.singleton;
     2 
     3 public class Main {
     4 
     5     public static void main(String[] args) {
     6      
     7         Singleton object1= Singleton.getInstance();
     8         Singleton object2= Singleton.getInstance();
     9         if(object1==object2){
    10              System.out.println("是同一个对象...");
    11         }else{
    12              System.out.println("不是同一个对象...");
    13         }
    14         
    15     }
    16     
    17 }

    运行结果:

    下面我们对问题进行深入研究,

      2.2.1、将单例模式的定义改一下:

    1  public  static final Singleton singleton=new Singleton();

      变成公有的,便于我们在外面调用,并且是final类型的,那就必须赋值了,这是一个怪异的组合,虽然满足了静态final类型但是却不能在编译的时候初始化,因为后面用到了new关键字,因此在这个时候就会初始化,我们将Main改成如下所示:

     1         Singleton object1;
     2         System.out.println(Singleton.singleton+"-------------");
     3         
     4         object1= Singleton.getInstance();
     5         System.out.println("-------------");
     6         Singleton object2= Singleton.getInstance();
     7         System.out.println("-------------");
     8         if(object1==object2){
     9              System.out.println("是同一个对象...");
    10         }else{
    11              System.out.println("不是同一个对象...");
    12         }

    运行结果:

      可以看到确实如我们想要的结果,在静态部分初始化了。

      2.2.2、我们将Singleton代码恢复成原样,在main中改成如下所示,看一看到如果我们只是定义了一个变量,或者将其赋值为null,类初始化是不会开始的,只有我们调用了静态方法的时候才开始初始化。

     1         Singleton object1;
     2         object1=null;
     3         System.out.println("-------------");
     4         object1= Singleton.getInstance();
     5         System.out.println("-------------");
     6         Singleton object2= Singleton.getInstance();
     7         System.out.println("-------------");
     8         if(object1==object2){
     9              System.out.println("是同一个对象...");
    10         }else{
    11              System.out.println("不是同一个对象...");
    12         }

    结果:

      2.2.3、然后我们再将Singleton添加一个字段:

    1    public static  int sum=100;

    然后main中:

     1         System.out.println(Singleton.sum);
     2         System.out.println("-------------");
     3 
     4         Singleton object1;
     5         object1=null;
     6         System.out.println("-------------");
     7         object1= Singleton.getInstance();
     8         System.out.println("-------------");
     9         Singleton object2= Singleton.getInstance();
    10         System.out.println("-------------");
    11         if(object1==object2){
    12              System.out.println("是同一个对象...");
    13         }else{
    14              System.out.println("不是同一个对象...");
    15         }

    结果:

      2.2.4、最后我们将sum变量改成:

    1    public static final int sum=100;

    结果:

    可以看到确实是如果有final的常量不会触发类加载器,使用的时候可以看做常量,而没有加final的变量,一旦使用就会先初始化。

       由此得出一个深刻的结论,我们的对象初始化的时间是Singleton object1= Singleton.getInstance();中的Singleton.getInstance(),当这个方法执行的时候,要做这几件事情,首先就是看一下自己有没有父亲,如果有先初始化父亲的静态变量,然后初始化自己的静态变量private  static  Singleton singleton=new Singleton();之后初始化父类的构造器中的变量,最后初始化自己构造器之中的方法,到了最后一切进行完毕,才开始调用Singleton.getInstance()方法,这个时候singleton早就有对象了,因此将对象返回。因此当打印出“开始初始化对象”的时候(执行自身构造器方法),其实作为静态变量的singleton早就已经初始化成功了,这里有一定的误导之嫌,望读者注意。那么到底要不要在private  static  Singleton singleton=new Singleton();中加final呢?其实理解到这种程度我们就知道无所谓了,因为是私有,只有自己能使用,定义final想把自己当做编译时就能处理的常量,可是又使用了new关键字,就失去了这个作用了,因此有和没有一点关系都没有,当然这是不考虑反射的时候。

       2.3、那么在有的教材中还有一些单例方法,比如称我们这里的方法为饿汉式方法,而其他的又懒汉式方法,懒汉式方法总是会出现这样或那样的问题的,因为考虑到了多线程机制,实现起来比较麻烦,并且还会出现问题,就算是使用了一定的解救办法(同步、加锁、双重判断)的办法,性能还是被损耗了,因此懒汉式方法的弊端非常大,但是因为‘懒’,所以可以晚一点加载,占用的内存就会晚一点,这样可以为之前的进程节省空间,这也是从辩证法角度看问题的必然结果,凡事有利必有弊。我们简单看一下吧:

     1 package zyr.dp.singleton;
     2 
     3 public class SingletonLazy {
     4    private  static  SingletonLazy singletonLazy=null;
     5 
     6    private SingletonLazy(){
     7        System.out.println("开始初始化对象...");
     8    }
     9    public static SingletonLazy getInstance(){
    10        if(singletonLazy==null){
    11            return new SingletonLazy();
    12        }
    13        return singletonLazy;
    14    }
    15 }

      上面的代码就是错误的,如果产生多线程,那么就有可能全部进入if(singletonLazy==null)的逻辑之中,最后产生很多的SingletonLazy()对象。我们可以使用加锁机制,加入关键字synchronized就可以了,这个关键字的内部逻辑其实就是临界区的概念,特别的浪费CPU性能。

    1    public static synchronized SingletonLazy getInstance(){
    2        if(singletonLazy==null){
    3            return new SingletonLazy();
    4        }
    5        return singletonLazy;
    6    }

      或者使用如下方式,双重判断,第二次判断就是防止已经有一个对象产生了,因此也可以达到相应的目的。

     1    public static  SingletonLazy getInstance(){
     2        if (singletonLazy == null) {    
     3            synchronized (Singleton.class) {    
     4               if (singletonLazy == null) {    
     5                   singletonLazy = new SingletonLazy();   
     6               }    
     7            }    
     8        }    
     9        return singletonLazy;
    10    }

      当然还有其他补救措施,在这里不一一列举。

      2.4、一个例子

    Parent类:

     1 package zyr.dp.classloader;
     2 
     3 public class Parent {
     4 
     5     //protected static String familyName = "朱彦荣";
     6     
     7     public  Parent(){
     8         System.out.println("父类构造器初始化..."); 
     9     }
    10     
    11     static { 
    12         System.out.println("父类 静态代码块 被初始化"); 
    13     }
    14     { System.out.println("父类 非静态代码块 被初始化");}
    15 }

    Child类:

     1 package zyr.dp.classloader;
     2 
     3 public class Child extends Parent{
     4 
     5     public  Child(){
     6         System.out.println("子类构造器初始化..."); 
     7     }
     8     
     9     static { 
    10         System.out.println("子类 静态代码块 被初始化"); 
    11     }
    12     
    13     {System.out.println("子类 非静态代码块 被初始化");}
    14     
    15 }

    Main:

     1 package zyr.dp.classloader;
     2 
     3 public class Main {
     4 
     5     public static void main(String[] args) {
     6         Child child = new Child(); 
     7         //System.out.println(Child.familyName);
     8     }
     9 
    10 }

    结果:

    在父类中加入:

    1 protected static String familyName = "朱彦荣";

    main:

     1 package zyr.dp.classloader;
     2 
     3 public class Main {
     4 
     5     public static void main(String[] args) {
     6         //Child child = new Child(); 
     7         System.out.println(Child.familyName);
     8     }
     9 
    10 }

    结果:(子类一点也没有被初始化,父类被初始化)

    改成:(常量,编译时决定)

    1 protected static final String familyName = "朱彦荣";

    三、总结

      在这个单例模式中,我希望大家不要只知道单例的思想,更要知道类的加载和初始化时机,以及多线程的机制,我想这才是真正有意义的呢。

      程序代码

  • 相关阅读:
    毕设2020.02.02
    架构师眼中的高并发架构
    云时代架构读后感二
    以《淘宝网》为例,描述质量属性六个常见场景
    《架构漫谈》读后感
    云时代架构读后感一
    暑假周总结报告08
    暑假周总结报告07
    暑假周总结报告06
    假期周总结报告05
  • 原文地址:https://www.cnblogs.com/zyrblog/p/9224249.html
Copyright © 2020-2023  润新知