单例模式 代码以及祥解

单例模式的

本质:

  控制实例数目

定义:

  保证一个类仅有一个实例,并提供一个它的全局访问点。

  单例模式有两种实现方式:饿汉模式和懒汉模式。

懒汉式实现代码

public class Singleton{

  private static Singleton uniqueInstance = null;

  private Singleton(){}

  private static synchronized Singleton getInstance(){

    if(uniqueInstance == null){

      uniqueInstance = new Singleton();

    }  

    return uniqueInstance;

  }

}

饿汉式实现代码:

public class Singleton{

  private static Singleton uniqueInstance = new Singleton();

  private Singleton(){}

  private static synchronized Singleton getInstance(){ 

    return uniqueInstance;

  }

}

单例模式实现

  1.私有化构造函数;

  2.提供获取实例的方法;

  3.把获取实例的方法变为静态;

  4.定义存储实例的属性;

  5.把这个属性也定义为静态的;

  6.实现控制实例的实现(懒汉式);

  饿汉式实现方式体现了延迟加载的思想(简单说就是等到用这个资源的时候才加载,节约了资源);同时还体现了缓存的思想,当一个资源被频繁利用的时候,如果每次操作都从数据库或者硬盘上获取会很慢,所有有一种方式是存到内存中,要用到的时候,现在内存里面找,有就直接使用,从而节省大量时间。缓存是一种典型的利用空间换时间的方案

时间和空间

  比较两种实现方式:懒汉式是典型的时间换空间,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,就一直不被创建,则节约内存空间。

            饿汉式是典型的空间换时间,当类装载时就会被创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节约了运行时间。

线程安全

  从线程安全的角度来看,不加同步的懒汉式单例模式是线程不安全的,比如,有两个线程,一个A线程,一个B线程,它们同时调用getInstance()方法,那就可能导致并发问题,实例如下:

  

   

  饿汉式是线程安全的,因为虚拟机保证只会装载一次,在装载类的时候是不会发生并发的。

实现懒汉式的线程安全

  1.加synchronized,降低了访问效率,而且每次都还需要判断。

   2.“双重检测加锁”的方式实现,既使线程安全,又使性能不受太大影响,实现:

    使用到volatile关键字:被volatile修饰的变量的值,将不被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。

    其实使用volatile和不使用都是线程安全的(主要是synchronized代码块保证线程安全),主要作用不是在线程安全,而是禁止在多线程的情况下代码重排序(new Singleton()时可能会出现重排序)

    

    根据上图步骤解析(个人理解):例如有两个线程,A线程运行了伪代码的1,3步骤还没有运行到2,此时B线程进入方法2步骤,它会对变量进行取值操作,如果没有volatile的话它会直接执行取值,此时值为null,B线程就会往下执行,到synchronized代码块进行排队;如果有volatile,变量就不会重排序,取值操作会在执行完伪代码2步骤后执行,此时取值操作就会取到对象,线程直接会输出对象。还有一种理解是, CPU缓存中的volatile字段被一个线程修改后,其他CPU缓存中的线程在读 本地CPU缓存的 volatile字段时,就必须读取更新过的字段,所以B线程操作变量必须等A线程更新完才进行。

(频繁更改、改变或写入volatile字段 有可能导致性能问题)

     实现代码:

      public class Singleton{  

        private volatile static Singleton uniqueInstance = null;

        private Singleton(){}

        public static Singleton getInstance(){
          if(uniqueInstance == null){
            synchronized(Singleton.class){
              if(uniqueInstance == null){
                uniqueInstance = new Singleton();
              }
            }
          }
          return uniqueInstance;
        }

      }

 更好单例实现模式

  解决方案(Lazy initialization holder class模式)

    采用静态初始化器的方式,它可以由JVM来保证线程的安全性。

    实现代码:

      public class Singleton{  

        private Singleton(){}

           private static class SingletonHolder{

            private static Singleton instance = new Singleton();
         }
         public static Singleton getInstance(){
            return SingletonHolder.instance;
         }

      }

  静态内部类和它外部类没有直接联系,只有在第一次调用getInstance()方法,第一次读取SingletonHolder.instance时,SingletonHolder类得到初始化,而这个类在装载并被初始化的时候才会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,所有只会在虚拟机装载类的时候初始化一次,并由虚拟机保证它的线程安全性。

  优势:getInstance方法并没有被同步,并只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。

用枚举来实现单例(最佳方法)

  用枚举实现单例非常简单,只需要编写一个包含单个元素的枚举类型即可。

  实现代码:

  public enum Singleton{

    /**定义一个枚举的元素,他就代表了Singleton的一个实例**/

    uniqueInstance;

    /**单例也可以有自己的操作方法**/

    public void singletonOperation(){

    //功能处理

    }

  }

使用枚举实现单实例控制会更简洁,而且无偿的提供了序列化机制,并由JVM从根本上提供了保障,绝对防止多次实例化,是更简洁,高效,安全的实现方式。

原文地址:https://www.cnblogs.com/johnnyC/p/9432133.html