大家面试的免不了会让你写个单例模式,那么写就写呗:
public class SingletonObject1 {
/**
* can't lazy load.
*/
private static final SingletonObject1 instance = new SingletonObject1();
private SingletonObject1() {
//empty
}
public static SingletonObject1 getInstance() {
return instance;
}
}
好了面试官就会问你有没有什么要补充的啊?你说没了,那么恭喜你,这次面试凉凉喽.
我们分析撒,这样写虽然一点毛病没有但是你们发现没有它不能懒加载,也就去是说这是恶汉试的方式,由于是被静态代码块修饰它加载的时候会随着类加载,也就是说主动加载的方式那么它有什么问题呢?如果我们很长时间不使用它就会占用内存哦!!
懒加载的方式:
public class SingletonObject2 {
private static SingletonObject2 instance;
private SingletonObject2() {
//empty
}
public static SingletonObject2 getInstance() {
if (null == instance)
instance = new SingletonObject2();
return SingletonObject2.instance;
}
}
ok的我们这是一个懒加载的单例模式,但是问题又来了我们知道在多线程的环境下它是不安全的,有可能抢线程的时候会创建多个线程,问题来了怎么解决呢?当然是加锁喽:
public class SingletonObject3 {
private static SingletonObject3 instance;
private SingletonObject3() {
//empty
}
public synchronized static SingletonObject3 getInstance() {
if (null == instance)
instance = new SingletonObject3();
return SingletonObject3.instance;
}
}
我们看到前面的问题,貌似都解决了,但是还不够,我们这其中加了锁哦,会变成串行话的 也就是性能变差了,唉哪儿来这么多问题真是的解决:double check的方式:
也就是检查了2次我们以Netty ConstantPool中的 一段代码为例子解释一下
private T getOrCreate(String name) {
T constant = constants.get(name);
if (constant == null) {
final T tempConstant = newConstant(nextId(), name);
//如果没有的话那么就赋值
constant = constants.putIfAbsent(name, tempConstant);
if (constant == null) {
return tempConstant;
}
}
return constant;
}
也就说要返回Contant实例,对它进行了两次判定,阅读顺序从1 到5 依次执行查看,并且最后返回了该实例
我的例子:
public class SingletonObject4 {
private static SingletonObject4 instance;
private SingletonObject4() {
//---
}
//double check
public static SingletonObject4 getInstance() {
if (null == instance) {
synchronized (SingletonObject4.class) {
if (null == instance)
instance = new SingletonObject4();
}
}
return SingletonObject4.instance;
}
}
这样的话,我们加了个class锁,这样跟上面比有什么区别呢?
很显然嘛!!上面那个是不管什么情况下都会加锁,而下面这种doublecheck方式呢?加锁的情况只发生在线程争抢的情况下发现木有?
好了到现在我们解决了不能懒加载的问题的问题,又解决了在多线程的情况下可能产生多个实例的问题,加锁后解决了性能问题,你可能绝的肯定完美了,但是....我们还有问题,看着代码你大脑回路想一想会不会可能出现Null呢?
你肯定觉得我是在找茬,但是找茬的不是我可能是面试官哦(我就被这样问到过).....
其实这就涉及到底层的问题也就是构造过程中的重排序,
我想说的就是,单一个线程去拿单例的时候,可能这个类里面的很多构造方法没有构造完,也就是说没有初始化完成但是这个线程却拿走了使用了用到里面的参数的时候,发现没有构造出来,所以也就空指针啦!
为什么会出现这种情况,因为java在编译的过程中会进行优化重排序,等等,因为java有jrt,等用来提高性能,
编译阶段和运行阶段都会进行优化,
举个简单的栗子:
我们写了两个变量会问jvm在执行的时候肯定会执行int i=0吧?
但是不一定哦,有可能会先执行int j=10;
我说的是执行的过程,jvm之保证程序正常运行即可,也就是说参数正确即可,这个过程中jvm会进行一些编译器的优化,还有运行时的优化
好的既然问题来了,那么只是问题吧:
public class SingletonObject5 {
private static volatile SingletonObject5 instance;
private SingletonObject5() {
//
}
//double check add volatile
public static SingletonObject5 getInstance() {
if (null == instance) {
synchronized (SingletonObject4.class) {
if (null == instance)
instance = new SingletonObject5();
}
}
return SingletonObject5.instance;
}
}
我们加了个volatile,自己去百度volatile关键字的特性啦!!! 这个太暴力了,不让人jvm重排序不推荐
还有一种:具体就看我的注释吧:
public class SingletonObject6 {
private SingletonObject6() {
//构造私有化
}
//static 只会被初始化一次, 同时也只能会顺序执行 被修饰的主动加载,只有使用它才会被加载
//初始化,构建,是线程友好的不会被初始化两次
private static class InstanceHolder {
private final static SingletonObject6 instance = new SingletonObject6();
}
public static SingletonObject6 getInstance() {
return InstanceHolder.instance;
}
}
第三种,使用枚举,线程安全只会被装载一次
public class SingletonObject7 {
private SingletonObject7() {
}
private enum Singleton {
INSTANCE;//定义枚举的时候构造函数已近被创建
private final SingletonObject7 instance;
//只会被装载一次
Singleton() {
instance = new SingletonObject7();
}
//实例函数
public SingletonObject7 getInstance() {
return instance;
}
}
public static SingletonObject7 getInstance() {
return Singleton.INSTANCE.getInstance();
}
public static void main(String[] args) {
//简单的测试一下下:
IntStream.rangeClosed(1, 100)
.forEach(i -> new Thread(String.valueOf(i)) {
@Override
public void run() {
System.out.println(SingletonObject7.getInstance());
}
}.start());
}
}
好了好了,一个单例写了这么长时间,如果面试的时候这样回答,保证让面试官爱上你哦!!!