之前经常看到各种单例模式的面试分享,但是每个人分享的都会有些不同,在自己实现了以后发现,单例模式虽然不难,但是面试官总会含一些套路在里面。
首先什么是单例
就是使系统在运行时该对象只有一个实例的存在。原理很简单,下面来看看面试官一般会怎么问吧。
单例模式了解吗?手写一个
来上代码
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance != null) {
return uniqueInstance;
}
uniqueInstance = new Singleton();
return uniqueInstance;
}
}
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {
}
public static Singleton getUniqueInstance() {
return uniqueInstance;
}
}
我相信如果之前没有准备过,第一反应写出来的肯定是这两种中的一种,但是这么写只能算是最基础的,而且这么写的话都得明确区分两种的区别,第一种就是所谓的懒汉单例模式,为什么这么说,是因为加载对应的时候不会去初始化实例,而是一直等到调用getUniqueInstance()的时候才会去实例化,而第二种就是饿汉单例模式,就是一进来就会去实例化,但是因为是静态的所以只会实例化一次,等调用方法的时候就直接返回。 但是我要说,如果说让你手写单例模式,只写到这个程度的话,面试官估计就会开始提问别的了。
你这单例模式怎么保证线程安全呢?
第一反应肯定是加锁,用synchronize关键字,接着上代码。后面的讲解就在第一种懒汉模式上加代码来说明。
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
return uniqueInstance;
}
}
怎么通过双重校验锁来实现线程安全?
首先不要怕,拆分一下就是双重校验加锁,具体看代码
public class Singleton {
private static volatile Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
uniqueInstance 是否一定要volatile 修饰呢?
可能有细心的小伙伴发现了上面的代码多了一个volatile关键字,我这就来解释一下。 用volatile来修饰 是非常必要的,是因为uniqueInstance = new Singleton();代码最终实现是三个步骤,
- 首先给uniqueInstance分配内存空间
- 实例化
- 将uniqueInstance 指向分配的内存地址 在单线程的情况下确实不会出现问题,但是在多线程的线程下,由于JVM可能会出现指令重排的情况,比如刚分配完内存空间,就去指向实例化好的地址,但是这个时候还没有进行第二个步骤,也就是没有实例化,就会出现空指针异常。而volatile 关键字可以确保JVM的实现是按照顺序进行。
最后在分享一个模拟Spring容器实现单例模式
public class ContainerSingleton {
private static final Logger LOGGER = LoggerFactory.getLogger(ContainerSingleton.class);
private ContainerSingleton() {
}
private static Map<Object, Object> ioc = new ConcurrentHashMap<>();
public static Object getUniqueInstance(String className) {
synchronized (ioc) {
if (!ioc.containsKey(className)) {
Object obj = null;
try {
obj = Class.forName(className).newInstance();
ioc.put(className, obj);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
}
return ioc.get(className);
}
}
总结
单例模式的种类还是挺多的,需要了解一定的设计模型然后在去了解。