放放是一个刚毕业的学生,性别男,爱好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<Singleton> 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大量的时间片。