前言
之前写过一篇关于单例模式的文章研究:DCL单例模式研究,昨天晚上看到了一篇文章让我对单例模式究竟有多少种写法产生了好奇,本篇文章就来研究一下单例的写法种类。
关于单例模式
wiki百科中定义如下:
单例模式,也叫单子模式,是一种常用的软件设计模式,属于创建型模式的一种。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。
简单的说,单例模式就是确保一个运行中的类仅有一个实例,并提供一个全局访问点。
饿汉式写法
饱汉式单例写法,
public class Singleton {
private static Singleton INSTANCE = new Singleton();
public static Singleton getInstancec() {
return INSTANCE;
}
}
这种方式的写法在类加载时就完成了初始化,所以类加载很慢,但获取很快,而且保证了多线程环境下的并发问题。但这种写法用的并不多,大部分情况下是需要使用"懒加载"的写法。
懒汉式-单线程写法
public class Singleton {
private static Singleton INSTANCE = null;
public static Singleton getInstancec() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
这种写法在单线程环境下是能保证单例的,但并发环境下无法正常工作,于是有了线程安全的写法。
懒汉式-线程安全写法
public class Singleton {
private static Singleton INSTANCE = null;
public synchronized static Singleton getInstancec() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
简单粗暴,直接在方法体上上锁确保方法执行的时候不会有其他线程干扰。
懒汉式-双重检查锁写法(DCL)
public class Singleton {
private volatile static Singleton INSTANCE = null;
public static Singleton getInstancec() {
if (INSTANCE == null) {
synchroized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
关于双重检查锁写法,可以参考我写的DCL单例模式研究。
懒汉式-静态内部类写法
静态内部类的写法如下:
public Class Singleton {
private static Class StaticSingleton {
private static final Singleton INSTANCE = new Singleton();
}
public Singleton getInstance() {
return StaticSingle.INSTANCE;
}
}
如果直接用static来修饰一个成员变量,那么该变量在类加载时就会触发类加载机制,从而导致单例变成饿汉式的写法。
关于类加载的时机可以查看我的文章:JVM浅探-从JVM内存区域到字节码执行引擎
但如果把static域放到内部类中,那么内部类只要没有被调用,就不会触发类加载,灵活使用这个特性从而实现一种懒汉式的单例写法。
枚举写法
枚举默认就是线程安全的,所以不用担心多线程环境,而且还能防止反序列化、反射来破坏单例的唯一性。
直接枚举:
public enum Singleton {
INSTANCE;
public Singleton getInstance() {
return INSTANCE;
}
}
内部类枚举:
public class Singleton {
private enum EnumSingle {
INSTANCE;
private final Singleton instance;
EnumSingle() {
instance = new Singleton();
}
}
public Singleton getInstance() {
return EnumSingle.INSTANCE.instance;
}
}
上面其实除了枚举的写法,其他写法都可能被反射导致重新类加载,然后再次实例化。
自旋锁写法
java中的乐观锁是CAS,CAS也可以叫自旋锁。在这里,CAS的主要用法就是当多个线程尝试更新INSTANCE时,只会让一个更新成功。
关于CAS也可以参考我以前写的文章:多线程与高并发之CAS(Compare And Swap)
而java中CAS借助AtomicReference来实现:
public class Singleton {
private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>();
public static Singleton getInstance() {
while (true) { // 自旋锁
Singleton single = INSTANCE.get();
if (null != single) { // 如果INSTANCE已经被实例化
return single;
}
single = new Singleton();
if (INSTANCE.copareAndSet(null, single)) {
// 通过CAS将INSTANCE与null进行比较,如果内存中的ISNTANCE为null则修改
return single;
}
}
}
}
注释部分我已经加上了,根据注释理解应该不难。
ThreadLocal写法
ThreadLocal可以为每一个线程都提供了一个变量,这些变量在不同的线程中分别独立,可以把ThreadLocal看做一个Map<Thread, Object>,每个线程获取ThreadLocal变量时,总是以自身作为key获取这些变量。
public class Singleton {
private static final ThreadLocal<Singleton> INSTANCE = new ThreadLocal<Singleton>() {
@Override
protected Singleton initialValue() {
return new Singleton();
}
};
public Singleton getInstance() {
return INSTANCE.get();
}
}
这种写法可以做如下测试:
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + ":" + Singleton.getInstance());
System.out.println(Thread.currentThread().getName() + ":" + Singleton.getInstance());
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + ":" + Singleton.getInstance());
System.out.println(Thread.currentThread().getName() + ":" + Singleton.getInstance());
}).start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + ":" + Singleton.getInstance());
System.out.println(Thread.currentThread().getName() + ":" + Singleton.getInstance());
}).start();
}
结果如下:
main:com.zihua.opencv.Singleton@6d6f6e28
main:com.zihua.opencv.Singleton@6d6f6e28
Thread-0:com.zihua.opencv.Singleton@59a30351
Thread-0:com.zihua.opencv.Singleton@59a30351
Thread-1:com.zihua.opencv.Singleton@57f64e55
Thread-1:com.zihua.opencv.Singleton@57f64e55
可以看到,这里的单例是针对对于同一份线程而言的,ThreadLocal是以空间来换时间,个人认为使用ThreadLocal实现的单例有点勉强,因为并不是纯粹的全局单例。
总结
一共实现了单例的8种写法,主要的是饿汉写法、普通懒汉写法、普通锁写法、双重检查锁写法、静态内部类写法、枚举写法、CAS写法。
ThreadLocal写法仅做了解即可。