别踩面试官的坑——单例模式没有那么简单

之前经常看到各种单例模式的面试分享,但是每个人分享的都会有些不同,在自己实现了以后发现,单例模式虽然不难,但是面试官总会含一些套路在里面。

首先什么是单例

就是使系统在运行时该对象只有一个实例的存在。原理很简单,下面来看看面试官一般会怎么问吧。

单例模式了解吗?手写一个

来上代码

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();代码最终实现是三个步骤,

  1. 首先给uniqueInstance分配内存空间
  2. 实例化
  3. 将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);
    }
}

总结

单例模式的种类还是挺多的,需要了解一定的设计模型然后在去了解。

你可能感兴趣的:(java)