单例模式是一种常见的设计模式,写法也比较多,在这篇文章里面主要是对单例模式的各种写法进行一个介绍。
这篇文章的主要内容如下:
首先简单的介绍一下单例模式的使用场景
然后就是单例模式写法的介绍。
最后对单例模式进行一个总结
一、单例模式的介绍
比较官方的理解:
单例模式确保某个类只有一个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。
有一个通俗的理解,那就是在古代,全国就一个皇帝。如何确保一个皇帝?这就是单例模式。
二、单例模式的各种写法
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开发当中很少使用。