双重检查模式
public class singleton { private volatile static singleton singleton; //1:volatile修饰 private singleton (){} public static singleton getsingleton() { if (singleton == null) { //2:减少不要同步,优化性能 synchronized (singleton.class) { // 3:同步,线程安全 if (singleton == null) { singleton = new singleton(); //4:创建singleton 对象 } } } return singleton; } }
推荐理由:
为什么要使用volatile修饰?
虽然已经使用synchronized进行同步,但在第4步创建对象时,会有下面的伪代码:
memory=allocate(); //1:分配内存空间 ctorinstance(); //2:初始化对象 singleton=memory; //3:设置singleton指向刚排序的内存空间
当线程a在执行上面伪代码时,2和3可能会发生重排序,因为重排序并不影响运行结果,还可以提升性能,所以jvm是允许的。如果此时伪代码发生重排序,步骤变为1->3->2,线程a执行到第3步时,线程b调用getsingleton方法,在判断singleton==null时不为null,则返回singleton。但此时singleton并还没初始化完毕,线程b访问的将是个还没初始化完毕的对象。当声明对象的引用为volatile后,伪代码的2、3的重排序在多线程中将被禁止!
静态内部类模式
public class singleton { private singleton(){ } public static singleton getsingleton(){ return inner.instance; } private static class inner { private static final singleton instance = new singleton(); } }
推荐理由:
如何实现线程安全?
线程a和线程b同时试图获得singleton对象的初始化锁,假设线程a获取到了,那么线程b一直等待初始化锁。线程a执行类初始化,就算双重检查模式中伪代码发生了重排序,也不会影响线程a的初始化结果。初始化完后,释放锁。线程b获得初始化锁,发现singleton对象已经初始化完毕,释放锁,不进行初始化,获得singleton对象。
在涉及到反射和序列化的单例中,建议使用下文的枚举类型模式。
其他类型的单例模式
懒汉模式(多线程不安全)
public class singleton { private static singleton instance = new singleton(); private singleton (){} public static singleton getinstance() { return instance; } }
饿汉单例模式(多线程安全)
public class singleton { private static singleton instance = new singleton(); private singleton (){} public static singleton getinstance() { return instance; } }
饿汉模式的线程安全同样通过类加载解决同步问题,但没有达到懒加载目的。(这里非常感谢之初z-chu的指正)
枚举单例模式(多线程安全)
public enum singleton { instance; public void dosomething(){ //todo dosomething } }
在joshua bloch大神的《effective java》是推荐该方法的。虽然线程安全,在实际开发中,还没有被广泛采用。因为太过简洁以致于可读性较差,还没有在实战中被广泛推广。枚举单例模式的线程安全同样利用静态内部类中讲到类初始化锁。枚举单例模式能够在序列化和反射中保证实例的唯一性。
高手之间的过招,必选择枚举单例模式。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对移动技术网的支持。
如对本文有疑问, 点击进行留言回复!!
【java基础】面试常见问题:类和对象,封装继承多态,final关键字,static关键字,类加载过程,双亲委派模型
荐 Java语言基础之JDK1.8新特性(Lambda表达式、函数式接口、Stream流、新的日期API)
网友评论