初识单例

单例模式,算是我们代码中经常遇见的设计模式之一了。当然我们也上手很快,但是其中的坑也不少,不好好研究一下,这些坑还真不好跳过去。单例简单分分别为懒汉模式、饿汉模式,那我们就从懒汉模式开始吧。

懒汉模式(线程非安全)

这里定义一个私有的全局变量singletonPattern,然后通过一个公有的静态方法对singletonPattern进行判空,如果为空,就new一个类对象出来,然后返回该对象。该种方式可以实现类对象在使用的时候才创建,也就是延时加载。

  1. 1 public class SingletonPattern {
  2. 2
  3. 3 private static SingletonPattern singletonPattern = null;
  4. 4
  5. 5 private SingletonPattern() {
  6. 6 }
  7. 7
  8. 8 public static SingletonPattern getInstance(){
  9. 9 // if这里存在竞态条件
  10. 10 if(singletonPattern == null){
  11. 11 singletonPattern = new SingletonPattern();
  12. 12 }
  13. 13 return singletonPattern;
  14. 14 }
  15. 15 }

懒汉模式(线程安全、低效)

一种比较简单的方式,是同步获取实例化的方法getInstance(),也就是加上synchronized关键字。当然这种方式是非常低效的(jdk后面的版本对synchronized关键字段的底层代码做了很强的优化,所以也不是不可以考虑),具体如下:

  1. 1 public class SingletonPattern {
  2. 2
  3. 3 private static SingletonPattern singletonPattern = null;
  4. 4
  5. 5 private SingletonPattern() {
  6. 6 }
  7. 7
  8. 8 public static synchronized SingletonPattern getInstance(){
  9. 9 if(singletonPattern == null){
  10. 10 singletonPattern = new SingletonPattern();
  11. 11 }
  12. 12 return singletonPattern;
  13. 13 }
  14. 14 }

 懒汉模式(线程安全、高效)

1、双重锁校验DCL(半成品,问题代码,面试考点),注意看下面罗列的四步。

  1. 1 public class SingletonPatternDcl {
  2. 2
  3. 3 private static SingletonPatternDcl singletonPatternDcl = null;
  4. 4
  5. 5 private SingletonPatternDcl() {
  6. 6 }
  7. 7
  8. 8 public static SingletonPatternDcl getInstance(){
  9. 9 if(singletonPatternDcl == null){ //1、在实例化的情况下,不需要执行加锁动作,性能提高
  10. 10 synchronized (SingletonPatternDcl.class){ //2、对类上锁,多个线程的情况下,只有一个线程能够创建对象
  11. 11 if(singletonPatternDcl == null){ //3、实例化对象为空的情况下创建对象
  12. 12 singletonPatternDcl = new SingletonPatternDcl(); //4、创建对象
  13. 13 }
  14. 14 }
  15. 15 }
  16. 16 return singletonPatternDcl;
  17. 17 }
  18. 18 }

2、完美的DCL。上面的DCl看起来是非常完美的,所有的逻辑都考虑到了,但是上面的第四步singletonPatternDcl = new SingletonPatternDcl()创建对象的过程其实并非是一个原子操作,这就导致了问题的产生。我们来分析一下第四步在JVM中具体做了哪些事情:

  • a、给singletonPatternDcl分配内存空间
  • b、调用SingletonPatternDcl的构造函数来初始化该成员变量
  • c、将singletonPatternDcl对象指向a步骤分配的内存空间(这一步执行完之后,singletonPatternDcl就为非null了)

而在JVM的即时编译器中存在指令重排序的优化,如果c步骤在b步骤之前执行的话:b执行了,singletonPatternDcl不为空了,第二个线程来了,发现singletonPatternDcl已经不为null了,然后直接返回。但是其实这个时候singletonPatternDcl只是一个内存地址,根本还没有初始化,程序就理所当然的报错了。解决的方法很简单,基于volatile解决方案,如下所示:

   private static volatile SingletonPatternDcl singletonPatternDcl = null;                                                                       

volatile的特性禁止指令重排序,保证了上述a、b、c一定会按着abc的顺序执行,也就避免了上述产生问题的场景。

 饿汉模式(天然的线程安全)

利用类加载的机制,我们可以在类一开始加载的时候就初始化一个实例对象。缺点是无法实现懒加载,并且在某些需要使用动态参数的情况下无法使用。

  1. 1 public class SingletonPatternSafe {
  2. 2
  3. 3 private static SingletonPatternSafe singletonPatternSafe = new SingletonPatternSafe();
  4. 4
  5. 5 private SingletonPatternSafe() {
  6. 6 }
  7. 7
  8. 8 public SingletonPatternSafe getInstance() {
  9. 9 return singletonPatternSafe;
  10. 10 }
  11. 11 }

这里加上final也是可以的

  1. private static final SingletonPatternSafe singletonPatternSafe = new SingletonPatternSafe();

静态内部类(天然的线程安全)

这种方式的单例实现,也是基于JVM本身机制保证了线程安全。其内部类Holder只有getInstance()方法可以访问。读取的实例的时候也不需要进行同步,没有性能的损失。

  1. 1 public class SingletonPatternHolder {
  2. 2
  3. 3 private static class Holder {
  4. 4 private static final SingletonPatternHolder INSTANCE = new SingletonPatternHolder();
  5. 5 }
  6. 6
  7. 7 private SingletonPatternHolder() {
  8. 8 }
  9. 9
  10. 10 public static SingletonPatternHolder getInstance(){
  11. 11 return Holder.INSTANCE; //懒汉式的,只有访问getInstance()方法的时候才实例化
  12. 12 }
  13. 13
  14. 14 }

枚举方式(绝对的线程安全)

枚举实现单例模式有三个特性:自由序列化、线程安全、保证单例。

  • enum的实现是通过继承了Enum类来实现的,enum结构不能作为子类来继承其他类,但是可以用来实现接口类;
  • 由于enum内部的实现方式其实是final类型的,所以enum类不可以被继承;
  • enum有且仅有private构造器,防止外部的额外构造,这恰好和单例模式相符合;
  • 其内部也是枚举量未被初始化,之后会在静态代码中进行初始化,这就非常类似饿汉模式;
  • 对于序列化和反序列化,因为每一个枚举类型和枚举变量在JVM中都是唯一的,所以Java在序列化和反序列化枚举时做了特殊规定,枚举的writeObject、readObject、readReplace和readResolve等方式是被编译器禁止的,因此不存在实现序列化接口之后调用readObject会重新创建的心得对象从而破坏单例的问题。

基于上述描述,我们发现enum的方式来构造单例模式,代码实现起来非常的简单、自由序列化。并且也是线程的安全,相比起来应该更优选择

  1. 1 public enum SingletonPatternEnum {
  2. 2
  3. 3 /**
  4. 4 * 实例化对象
  5. 5 */
  6. 6 INATANCE
  7. 7 }

代码实例

我的代码放在GitHub,小伙伴可以作为一个参考、

参考博文

  1. SingletonPattern、我们平常用到的一个设计模式,有必要深入学习,掌握精髓,在实战中灵活运用。感谢前辈们的分享做为引路人。-------书山有路、人儿需行<<

版权声明:本文为yrml原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/yrml/p/9132244.html