实现Singleton模式

设计一个类,只能生成该类的一个实例

只能生成一个实例的类是实现了单例模式的类型。由于设计模式在面向对象程序设计中起着举足轻重的作用,面试过程中很容易被问到。

 

方法一:只适用于单线程环境

生成一个实例,必须把构造函数设计成为私有函数,以禁止他人创建实例。我们可以定义一个静态的实例,在需要的时候创建该实例

public sealed class Singleton1{
  private Singlaeton1(){
  }
  private static Singleton1 instance=null;
  public static Singleton1 Instance{
    get{
      if(instance==null){
        instance=new Singleton1();
      }
      return instance;
    }
  }
}

引申知识

instance的用法:

getInstance这个方法在单例模式用的甚多,为了避免对内存造成浪费,直到需要实例化该类的时候才将其实例化,所以用getInstance来获取该对象

上述代码在Singleton1的静态属性Instance中,只有在instance为null的时候才创建一个实例以避免重复创建。同时我们把构造函数定义为私有函数,这样就能确保只创建和一个实例

 

(1)getInstance的使用:

①在主函数开始时调用,返回一个实例化对象,此对象是static的,在内存中保留着它的引用,即内存中有一块区域专门用来存放静态方法和变量

②可以直接使用,调用多次返回同一个对象

 

(2)getInstance 和 new的区别:

①大部分类都可以用new,new就是通过生产一个新的实例对象,或者在栈上声明一个对象,每部分的调用都是用的一个新的对象

②getInstance在单例模式(保证一个类仅有一个实例,并提供一个访问它的全局访问点)的类中常见,用来生成唯一的实例,getInstance往往是static的

③对象使用之前通过getInstance得到而不需要自己定义,用完之后不需要delete。new 一定要生成一个新对象,分配内存;getInstance() 则不一定要再次创建,它可以把一个已存在的引用给你使用,这在效能上优于new

 new创建后只能当次使用,而getInstance()可以跨栈区域使用,或者远程跨区域使用。所以getInstance()通常是创建static静态实例方法的

⑤对于抽象类,是只能用getInstance()方法,是不能new出来的

 

方法二:多线程效率低的方法

当两个线程同时运行到判断instance是否为null的if语句,斌并且instance的确没有创建时,那么这两个线程都会创建一个实例,此时类型Singleton1就不再满足单例模式的要求了。

public sealed class Singleton2{
  private Singleton2(){
  }
  private static readonly object syncObj = new object();
  private static Singleton2 instance=null;
  public static Singleton2 Instance{
    get{
      lock(syncObj){
        if(instance==null){
          instance=new Singleton2();
        }
      }
      return instance;
    }
  }
}

在一个时刻只有一个线程能得到同步锁,当第一个线程加上锁的时候,第二个线程只能等待。当第一个线程发现实例还没有创建时,它创建一个实例,接着第一个线程释放同步锁,第二个线程就可以加上同步锁,并运行接下来的代码。这时,由于实例已经被第一个线程创建出来了,第二个线程就不会重复创建,保证在多线程环境中只得到一个实例。

但是Singleton2不完美,每次通过属性Instance得到Singleton2的实例都会加上一个同步锁,而加锁非常耗时。

 

方法三:加同步锁前后两次判断实例是否已经存在

只是在实力还没创建之前需要加锁操作,保证只有一个线程创建出实例。当实例已经创建之后,我们已经不需要在执行加锁操作

public sealed class Singleton3{
  private Singleton3(){
  }
  private static object syncObj=new object();
  private static Singleton3 instance=null;
  public static Singleton3 Instance{
    get{
      if(instance==null){
        lock(syncObj){
          if(instance==null){
            instance=new Singleton3();
          }
        }
      }
      return instance;
    }
  }
}

Singleton3用加锁机制来确保在多线程环境下只创建一个实例,并且用两个if判断来提高效率。这样的代码是下起来比较复杂,容易出错

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}

第一次校验:由于单例模式只需要创建一次实例,如果后面再次调用getInstance方法时,则直接返回之前创建的实例,因此大部分时间不需要执行同步方法里面的代码,大大提高了性能。如果不加第一次校验的话,那跟上面的懒汉模式没什么区别,每次都要去竞争锁。

第二次校验:如果没有第二次校验,假设线程t1执行了第一次校验后,判断为null,这时t2也获取了CPU执行权,也执行了第一次校验,判断也为null。接下来t2获得锁,创建实例。这时t1又获得CPU执行权,由于之前已经进行了第一次校验,结果为null(不会再次判断),获得锁后,直接创建实例。结果就会导致创建多个实例。所以需要在同步代码里面进行第二次校验,如果实例为空,则进行创建。

需要注意的是,private static volatile SingleTon3 singleTon=null;需要加volatile关键字,否则会出现错误。问题的原因在于JVM指令重排优化的存在。在某个线程创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化。若紧接着另外一个线程来调用getInstance,取到的就是状态不正确的对象,程序就会出错。

 

方法四:实现按需创建实例

利用私有嵌套类型的特性,做到只有在真正需要的时候才会创建实例,提高空间使用效率。

public sealed class Singleton4{
  Singleton4(){}
  public static Singleton4 Instance{
    get{
      return Nested.instance;
    }
  }
  class Nested{
    static Nested{}
    internal static readonly Singleton4 instance=new Singleton4();
  }
}

我们在内部定义了一个私有类型Nested,当第一次用到这个嵌套类型的时候,会调用静态构造函数创建Singleton4的实例instance。类型Nested只在属性Singleton4.Instance中被用到,由于其私有属性,他人无法使用Nested类型。因此当我们第一次试图通过属性Singleton4.Instance得到Singleton4的实例时,会自动调用Nested的静态构造函数创建实例instance。如果我们不调用属性Singleton4.Instance就不会触发,也不会创建实例,这样就真正做到了按需创建

 

引申知识:

sealed表示他不能作为其他类型的基类

 

 

posted @ 2021-09-30 16:57  Heinrich♣  阅读(42)  评论(0)    收藏  举报