单例模式是一种常见的设计模式,写法也比较多,在这篇文章里面主要是对单例模式的各种写法进行一个介绍。

这篇文章的主要内容如下:

首先简单的介绍一下单例模式的使用场景

然后就是单例模式写法的介绍。

最后对单例模式进行一个总结

一、单例模式的介绍

比较官方的理解:

单例模式确保某个类只有一个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。

有一个通俗的理解,那就是在古代,全国就一个皇帝。如何确保一个皇帝?这就是单例模式。

二、单例模式的各种写法

1、懒汉式:基本写法

public class Singleton {
   private Singleton() {}//构造方法
   private static Singleton single=null
  
   public static Singleton getInstance() { 
        if (single == null) {  
            single = new Singleton();
        }  
       return single;
  }
}

特点:

  • 线程不安全(并发时可能出现多个单例)

  • 构造方法为private,限定了外部只能从getInstance去获取单例

  • 使用static关键字,表明全局只有一份节约了资源,但第一次加载在getInstance()需要实例化,需要一定时间。

2、懒汉式:使用synchronized 同步

public class Singleton {  
   private static Singleton instance;  
   private Singleton (){} 
   
   public static synchronized Singleton getInstance() {  
  if (instance == null) {  
      instance = new Singleton();  
  }  
  return instance;  
  }  
}  

特点:

  • 线程安全

  • 效率太低(synchronized)

  • 耗内存

3、懒汉式:双重检查锁定

public class Singleton{
private static Singleton instance;  
   private Singleton (){} 
   
public static Singleton getInstance() {
  //第一次检查
       if (singleton == null) {  
     synchronized (Singleton.class) { 
             //第二次检查
        if (singleton == null) {  
           singleton = new Singleton(); 
        }  
    }  
  }  
  return singleton
}
}

特点:

  • 线程安全

  • 比较常用,但是synchronized依然有一定的性能影响


4、饿汉式:基本写法(instance为private)

public class Singleton {
   private Singleton() {}
   //提前创建一个Singleton
   private static final Singleton instance = new Singleton();
   //有调用者直接就拿出来给了
   public static Singleton getInstance() {
       return instance;
  }
}

特点:

  • 线程安全(因为提前创建了,所以是天生的线程安全)

  • instance在类加载时就实例化


5、饿汉式:基本写法(instance为public)

public class Singleton {
   public static final Singleton instance = new Singleton();
   private Singleton() {}
}

特点:

  • 简单

  • 带来一定的效率问题


6、饿汉式:静态代码块

public class Singleton {
   private Singleton instance = null;
   private Singleton() {}
// 初始化顺序:基静态、子静态 -> 基实例代码块、基构造 -> 子实例代码块、子构造
   static {
       instance = new Singleton(); 
  }

   public static Singleton getInstance() {
       return this.instance;
  }
}

特点:

  • 线程安全

  • 类初始化时实例化 instance


7、静态内部类

public class Singleton {  
//静态内部类里面创建了一个Singleton单例
   private static class InstanceHolder {  
      private static final Singleton INSTANCE = new Singleton();  
  }  
   private Singleton (){}  
   public static final Singleton getInstance() {  
      return InstanceHolder.INSTANCE;  
  }  
}  

特点:

  • 线程安全

  • 效率高,避免了synchronized带来的性能影响


8、枚举式

public enum Singleton {
   INSTANCE;
   // 枚举同普通类一样,可以有自己的成员变量和方法
   public void getInstance() {
       System.out.println("Do whatever you want");
  }
}

特点:

  • 线程安全(枚举类型默认就是安全的)

  • 避免反序列化破坏单例


9、CAS方式

public class Singleton {
  // AtomicReference 是原子引用类型
  private static final AtomicReference<Singleton> INSTANCE 
  = new AtomicReference<Singleton> ();

  private Singleton() {}

  public static Singleton getInstance() {
      for(;;) {
          Singleton instance = INSTANCE.get();
          if(instance != null) {
              return instance;
          }
          instance = new Singleton();
          // CAS 方法有两个参数 expect 和 update,以原子方式实现了比较并设置的功能
          // 如果当前值等于 expect,则更新为 update 并返回 true;否则不更新并返回 false
          if(INSTANCE.compareAndSet(null, instance)) {
              return instance;
          }
      }
  }
}

特点:

  • 优点:不需要使用传统的锁机制来保证线程安全,CAS 是一种基于忙等待的算法,依赖底层硬件的实现,相对于锁它没有线程切换和阻塞的额外消耗,可以支持较大的并行度

  • 缺点:如果忙等待一直执行不成功(一直在死循环中),会对 CPU 造成较大的执行开销。而且,这种写法如果有多个线程同时执行 singleton = new Singleton(); 也会比较耗费堆内存。


10、Lock机制

// 类似双重校验锁写法
public class Singleton {
      private static Singleton instance = null;
      private static Lock lock = new ReentrantLock();

      private Singleton() {}

      public static Singleton getInstance() {
          if(instance == null) {
              lock.lock(); // 显式调用,手动加锁
              if(instance == null) {
                  instance = new Singleton();
              }
              lock.unlock(); // 显式调用,手动解锁
          }
          return instance;
      }
}


当然还有一些其他的实现单例的写法,比如说登记式单例等等。

三、总结
单例模式是否推荐懒加载反序列化单例反射单例克隆单例性能、失效问题
饿汉模式推荐注意是急切初始化!
懒汉模式✔️存在性能问题
枚举推荐✔️✔️✔️✔️
静态内部类推荐✔️JDK < 1.5不支持
双重校验锁可用✔️JDK < 1.5 失效

有两种场景可能导致非单例的情况

  • 场景一:如果单例由不同的类加载器加载,那便有可能存在多个单例类的实例

  • 场景二:如果 Singleton 实现了 java.io.Serializable 接口,那么这个类的实例就可能被序列化和反序列化

单例的写法基本上就是这些,可能在不同的场景下使用不同的方式,对我来说,在后端更经常使用的就是枚举类型,但是Android开发当中很少使用。


更多相关文章

  1. 多线程环境下生成随机数
  2. 线程池调整真的很重要
  3. Java线程之线程的调度-休眠
  4. Java线程之线程的调度-优先级
  5. Java线程之线程的交互
  6. Java线程之线程的同步与锁
  7. Java线程之线程状态的转换
  8. Java线程之创建与启动
  9. Java线程之概念与原理

随机推荐

  1. PHP 排序算法之选择排序
  2. PHP 多进程和多线程的优缺点
  3. PHP 排序算法之插入排序
  4. PHP如何执行耗时脚本实时输出内容
  5. PHP 排序算法之希尔排序
  6. PHP非阻塞批量推送数据
  7. PHP实现微信支付(jsapi支付)流程的方法
  8. 关于PHP中单例模式的实现
  9. PHP怎么实现微信申请退款
  10. PHP随机生成不重复的8位卡号(数字)和卡密(字