今天写一下设计模式之中的单例模式,所谓单例模式其实就是保证在JVM 中一个类仅仅有一个实例。这意味着单例的类不能被new出来,所以我们一般会把构造方法设置为private,提供一个static的getInstace方法让用户调用。
1.懒汉式写法
这种写法是先定义一个instance,在用到的时候才会去new,所以一般称为懒汉式写法。
package com.sgx.singleton;
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton(){}
public static LazySingleton getInstance(){
if(instance == null){
instance = new LazySingleton();
}
return instance;
}
}
显然这种写法是非线程安全的,在多线程环境下,会有生成多个实例的情况。两个线程A和B,A判断了instance为null,进行实例的初始化,当A还未初始化完成的时候依然为null,线程B进行了实例的初始化;最终结果会得到两个实例,显然是非线程安全的。
2.饿汉式写法
这种写法会直接new instance出来,不管是否用到先new,所以称为饿汉式写法。
package com.sgx.singleton;
public class HungarySingleton {
private static final HungarySingleton instance = new HungarySingleton();
private HungarySingleton(){}
public static HungarySingleton getInstance(){
return instance;
}
}
饿汉式是线程安全的写法。
3.线程安全的懒汉式写法
我们知道,方法要线程安全,加上sychronized关键字即可。修改后的线程安全的代码如下:
package com.sgx.singleton;
public class SafeLazySingleton {
private static SafeLazySingleton instance;
private SafeLazySingleton(){}
public synchronized static SafeLazySingleton getInstance(){
if(instance == null){
instance = new SafeLazySingleton();
}
return instance;
}
}
4.双重检验锁写法
第三种写法虽然简单明了,但是很显然效率变低了,因为无论何时他只允许一个线程进行操作,但是想象一下,我们应该限制的其实只是当instance为空的时候,让他加锁,其他情况下是不用加锁的。所以有了双重检验锁的写法,代码如下:
package com.sgx.singleton;
public class DoubleCheckLock {
private static volatile DoubleCheckLock instance;
private DoubleCheckLock(){}
public static DoubleCheckLock getInstance(){
if(instance == null){
synchronized(DoubleCheckLock.class){
if(instance == null){
instance = new DoubleCheckLock();
}
}
}
return instance;
}
}
为啥叫双重检验锁?第一重,当多线程调用getInstance的时候,先让他们进来,如果instance实例不为空,那么直接return即可,不用加锁,这一点就比第三种写法快很多。这也是双重检验的第一重。第二次检验发生时,假设多个线程通过了第一次检验,线程A拿到锁进入synchronized块,需要对instance实例化,为了防止线程B不知道发生了什么事情,(当A释放掉instance.class的锁,线程B又要获得锁再次初始化,)防止这种事情发生,我们定义instance为volatile,这样当A初始化完,这个instance实例会被刷回主存,对B线程来说她也是可见的,所以B会直接返回instance的实例,而不会发生刚才那种危险的情况,再去new一个instance出来。
5.静态内部类写法
顾名思义,定义内部类,写法如下:
package com.sgx.singleton;
public class Inner {
private static final class Instance{
private static final Inner instance = new Inner();
}
private Inner(){}
public static Inner getInstance(){
return Instance.instance;
}
}
6.枚举写法
package com.sgx.singleton;
public enum I {
instance;
public I open(String box){
return getSugar(box);
}
}
因为是枚举类型,实际上反编译后知道是一个继承Enum的类,由于枚举的特性导致只有一个实例,并且是线程安全、反射安全以及反序列化安全。