称号:设计一个类。我们只能产生这个类的一个实例。(来自《剑指Offer》)

解析:仅仅能生产一个实例的类是实现Singleton(单例)模式的类型。因为设计模式在面向对象程序设计中起着举足轻重的作业,在面试过程中非常多公司都喜欢问一些与设计模式相关的问题。

在经常使用的模式中,Singleton是唯一一个可以用短短几十行代码完整实现的模式。

因此,写一个Singleton的类型是一个非经常见的面试题。

下面我们给出几种解法。供大家參考。

*不好的解法一:仅仅适用于单线程环境。



因为要求仅仅能产生一个实例。因此我们必须把构造函数设为私有函数以禁止他人创建实例。我们能够定义一个静态的实例,在须要的时候创建该实例。基于这个思路的实现:

public class Singleton1{

private Singleton1(){

}

private final static Singleton1 instance = null;

public static Singleton1 getInstance(){

if(instance == null)

instance = new Singleton1();

return instance;

}

}

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

*不好的解法二:尽管在多线程环境中能工作但效率不高

解法一中的代码在单线程的时候工作正常。但在多线程的情况下就有问题了。设想假设两个线程同一时候执行到推断instance是否为null的if语句。而且instance的确没有创建时,那么两个线程都会创建一个实例,此时类型Singleton1就不再满足单例模式的要求了。

为了保证在多线程环境下我们还是仅仅能得到类型的一个实例,须要加上一个同步锁。把Singleton1稍做改动得到了例如以下代码:


public class Singleton2 {

   private static Singleton2 instance = null;

   private Singleton2() { }





   public static synchronized Singleton2 getInstance() {

      if(instance == null) {

         instance = new Singleton2();

      }

      return instance;

   }

}

我们还是如果有两个线程同一时候想创建一个实例。因为在一个时刻仅仅有一个线程能得到同步锁。当第一个线程加上锁时,第二个线程仅仅能等待。当第一个线程发现实例还没有创建时,它创建出一个实例。接着第一个线程释放同步锁,此时第二个线程能够加上同步锁,并执行接下来的代码。这个时候因为实例已经被第一个线程创建出来了,第二个线程就不会反复创建实例了,这样就保证了我们在多线程环境中也仅仅能得到一个实例。

可是类型Singleton2还不是非常完美。我们每次通过属性Instance得到Singleton2的实例,都会试图加上一个同步锁,而加锁是一个非常耗时的操作。在没有必要的时候我们应该尽量避免。

**可行的解法一:加同步锁前后两次推断实例是否已存在

我们仅仅是在实例还没有创建之前须要加锁操作,以保证仅仅有一个线程创建实例。而当实例已经创建之后,我们已经不须要再加锁操作了。所以我们改进例如以下:

public class Singleton3 {

   private static Singleton3 instance = null;

   private Singleton3() { }

   public static Singleton3 getInstance() {

      if(instance == null) {

         synchronzied(Singleton3.class) {

            Singleton3 temp = instance;

            if(temp == null) {

               temp = new Singleton3();

               instance = temp

            }

         }

      }

      return instance;

   }

}

因为指令重排序问题,所以不能够直接写成以下这样:

public class Singleton3 {

   private static Singleton3 instance = null;

   private Singleton3() { }

   public static Singleton3 getInstance() {

      if(instance == null) {

         synchronzied(Singleton3.class) {

            if(instance == null) {

               instance = new Singleton3();

            }

         }

      }

      return instance;

   }

}

可是假设instance实例变量用volatile修饰就能够了,volatile修饰的话就能够确保instance = new Singleton();相应的指令不会重排序,例如以下的单例代码也是线程安全的:

public class Singleton3 {

   private static volatile Singleton3 instance = null;

   private Singleton3() { }

   public static Singleton3 getInstance() {

      if(instance == null) {

         synchronzied(Singleton3.class) {

            if(instance == null) {

               instance = new Singleton3();

            }

         }

      }

      return instance;

   }

}

Singleton3用加锁机制来确保在多线程环境下仅仅创建一个实例,而且用俩个if推断来提高效率。

这种代码实现起来比較复杂,easy出错,我们还有更优秀的解法。





**强烈推荐的解法二:借助内部类

这样的方法属于懒汉式单例,由于Java机制规定,内部类SingletonHolder仅仅有在getInstance()方法第一次调用的时候才会被载入(实现了lazy)。并且其载入过程是线性安全的。内部类载入的时候实例化一次instance。

public class Singleton {





   private Singleton() { }  

    private static class SingletonHolder {

      private final static Singleton INSTANCE = new Singleton();

   } 

    public static Singleton getInstance() {

      return SingletonHolder.INSTANCE;

   }

}

版权声明:本文博客原创文章,博客,未经同意,不得转载。

05-11 20:48