首先扯点别的:距离上次区华东理工玩已经很长时间了,等到8月份再去看看。
今天记录一下Java中单例模式的写法
单例模式:确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例
饿汉式单例类.在类初始化时,已经自行实例化,线程安全。
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
这种方式在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快。 这种方式基于类加载机制避免了多线程的同步问题。
懒汉式单例类.在第一次调用的时候实例化自己。
- 同步方法
public class Singleton {
private static Singleton instance;
private Singleton () {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这种写法能够在多线程中很好的工作,但是每次调用getInstance方法时都需要进行同步,造成不必要的同步开销,而且大部分时候我们是用不到同步的,所以不建议用这种模式。
- 双重检查
//volatile关键字在这里是为了避免指令重排
private volatile static Singleton INSTANCE;
private Singleton() {
}
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
//注释1处,
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
双重检查方式:第一次判断INSTANCE
是否为null,是为了不必要的同步。第二次判断是在INSTANCE
等于null的情况下才创建实例。比如有三个线程调用getInstance()方法,有可能三个调用都进入到了第一个if判断了,只有一个调用可以获得锁,如果不再判断一次INSTANCE 是否为空(第二个if),那么每一个获得锁的线程都会创建一个新的INSTANCE 对象,那么三个线程就会创建三个对象了,使用了if判断后,就保证只会创建一个对象。
volatile关键字的作用:一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义。
- 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,变量的新值对其他线程来说是立即可见的。
- 禁止进行指令重排。
我认为在这里使用volatile
修饰共享变量是为了禁止指令重排。
在双重检查的注释1处,可以分为3步。
- 在内存中开辟一块地址。
- 在地址上初始化Singleton对象。
- 将INSTANCE引用指向Singleton对象地址。
根据双重检查单例为什么要加volatile一文中有序性的分析;在双重检查的注释1处的代码执行顺序会有:1—>2—>3和1—>3—>2 两种情况,只要最终结果一致,在java内存中以上执行顺序都有可能发生。
问题就出在执行顺序上,1—>2—>3顺序执行不会有什么问题;如果执行1—>3—>2,我们推测会有什么情况发生:
如果A线程执行完了注释1处,B线程获取锁了,这个时候B线程发现INSTANCE不为null了,就直接返回INSTANCE。此时,如果执行顺序是1—>3—>2时,INSTANCE拿到对象地址,对象正在初始化中;
B线程获取锁返回INSTANCE,此时INSTANCE对象还未初始化完成或部分部分初始化,如果这个时候使用INSTANCE进行其他操作,会导致空指针或操作与预期结果不一致问题。
在INSTANCE前加volatile关键字,就能确保执行顺序为 1—>2—>3。
最终结论,双重检查单例利用了volatile的能禁止指令重排序特性,保证代码的有序性。
参考
单例模式singleton为什么要加volatile
双重检查单例为什么要加volatile
2 静态内部类方式。类只会加载一次。
private Singleton() {
}
private static class LazyLoader {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return LazyLoader.INSTANCE;
}
3 枚举方法
public enum Singleton {
INSTANCE;
public void method(){
System.out.println(" I am singleton");
}
}
使用枚举除了线程安全和防止反射强行调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象.因此,《Effective Java》推荐尽可能地使用枚举来实现单例。
参考链接