面试题集锦&&实现Singleton(单例)模式-JAVA版

题目:设计一个类,我们只能生产该类的一个实例。(来自《剑指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判断来提高效率。这样的代码实现起来比较复杂,容易出错,我们还有更优秀的解法。


**强烈推荐的解法二:借助内部类
这种方法属于懒汉式单例,因为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;
   }
}
 


 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值