大家可以看一下普通的单例模式,里面有懒汉式和饿汉式单例模式,对后面线程安全单例模式做个铺垫。
java 单例模式
我们这讨论的是创建时的线程安全的实现。
实现1
public final class Singleton implements Serializable {
private Singleton(){}
private static final Singleton INSTANCE=new Singleton();
public static Singleton getInstance() {
return INSTANCE;
}
public Object readResolve{
return INSTANCE;
}
}
-
1:类前加final
是害怕有子类继承,子类的一些方法破坏单例 -
2:我们看到如果类实现序列化接口implements Serializable,我们可以做一些什么来防止反序列化破话单例?
public Object readResolve{
return INSTANCE;
} -
3:这里创建实例是否有线程安全问题?
没有,private static final Singleton INSTANCE=new Singleton();有static,所以是在类加载阶段创建实例,类加载阶段会由jvm保护线程安全。
实现2:(枚举实现)
实现3
public final class Singleton implements Serializable {
private Singleton(){}
private static volatile Singleton instance=null;
public static Singleton getInstance() {
if (instance!=null){
return instance;
}
synchronized (Singleton.class){
if (instance!=null){
return instance;
}
instance=new Singleton();
return instance;
}
}
}
- private static volatile Singleton instance=null;为何需要用volatile修饰?
禁止指令重排,需要volatile关键字的原因是,在并发情况下,如果没有volatile关键字,在第5行会出现问题。instance = new TestInstance();可以分解为3行伪代码
a.memory = allocate() //分配内存
b. ctorInstanc(memory) //初始化对象
c. instance = memory //设置instance指向刚分配的地址
上面的代码在编译运行时,可能会出现重排序从a-b-c排序为a-c-b。在多线程的情况下会出现以下问题。当线程A在执行第5行代码时,B线程进来执行到第2行代码。假设此时A执行的过程中发生了指令重排序,即先执行了a和c,没有执行b。那么由于A线程执行了c导致instance指向了一段地址,所以B线程判断instance不为null,会直接跳到第6行并返回一个未初始化的对象。
- 2:为什么在synchronized代码块内在此判断一次是否为空呢?
当第二个线程来了在第一个判断空过了,synchronized这因为得不到锁,所以等在这,当第一个线程释放锁后再进入,这是就还需要判断一次是否为空。前面第一个判断是否为空是为了减少synchronized的加锁范围。
如果直接这样锁的范围有点大!
实现4
public final class Singleton implements Serializable {
private Singleton(){}
private static class lazy{
static final Singleton instance=new Singleton();
}
public static Singleton getInstance(){
return lazy.instance;
}
}
这个是在类加载时创建实例,static在类加载时JVM保护线程安全。