放放学单例设计模式

放放是一个刚毕业的学生,性别男,爱好Java。
放放的好朋友烬哥哥是一个优秀的架构师,看放放今天面色不太好
烬哥哥:出啥事了?
放放:面试官问了我,听过单例设计模式没?
烬哥哥:这个你没答上来,不科学。
放放:当然不是,面试官问了我更加深入的问题,你听我跟你港。

面试场景:
面试官:自我介绍一下。
放放:巴拉巴拉。。
面试官:看你简历上写到熟悉常见的设计模式,那你简单的说一下单例。
放放:单例设计模式是。。。
面试官:那你简单的手写一个。
放放:那我就写个简单的。

public enum Singleton{
  INSTANCE;
public static Singleton getInstance(){
  return  INSTANCE;
  }
}

面试官:写的可以,不过一般写可能会用懒汉和饿汉,你能简单的写一下吗?
放放:快速的写出来。。。

public class Singleton{
  public static Singleton instance = null;
  private Singleton(){}
  public static Singleton getInstance(){
    if(null == instance){
        instance = new Singleton();
    }
    return instance;
  }
}

面试官:很好,你这个懒汉式如果存在两个线程同时去调用getInstance方法,会产生两个instance吗?
放放心想:故意卖破绽,上当了,嘻嘻。
放放:当然了,并发的情况下有可能会产生多个对象。比如Thread1执行到

if(null == instance)

时间片用完,线程2开始执行也执行到上述条件下。
此时切换到线程1执行,返回一个对象实例。再切换到线程2又返回一个实例。
面试官:那该怎么解决呢?
放放:共享资源竞争问题,当然先加锁咯。用Synchronized关键字。

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

面试官:这样是可以,但是锁的力度太粗,效率很低。
放放:

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

放放:方法块去处理,粒度缩小了。双层检测来保证单例。第一个if保证非null的直接return不用经过加锁。第二个if是考虑到这样的情况。
有两个线程同时运行到第一个if。然后线程1拿到锁new一个对象,这时候切换到线程2,也会拿一个对象。所以为了杜绝这样的情况。做第二个判断。
面试官:这里还会出现一个问题你仔细想想。。
放放:没毛病啊。
面试官:静态变量需要加volatile关键字你知道为什么吗?
放放:。。。
面试官:(未完成!!!)
面试官:那你能考虑写出一种不用synchronize的懒汉式单例吗?
放放:。。。。。。
面试官:好了,你回去等通知吧。

场景回到现在:
烬哥哥:原来是这样哦,放放啊,你不是看过《并发编程的艺术》吗?还记得CAS吗?
放放:我靠,我当时怎么没想到。。。
烬哥哥:

public class Singleton {
    private static AtomicReference atomicReference = new AtomicReference<>();
    private Singleton(){}
    public static Singleton getInstance(){
        for(;;) {
            Singleton instance = atomicReference.get();
            if (instance != null) {
                return instance;
            }
            instance = new Singleton();
            if (atomicReference.compareAndSet(null, instance)) {
                return instance;
            }
        }
    }
}

烬哥哥:循环CAS来代替synchronized的锁,并发性更好点。缺点是:CAS消耗CPU大量的时间片。

你可能感兴趣的:(放放学单例设计模式)