1.饿汉式(线程安全,调用效率高,但是不能延时加载):JVM初始化的时候创建对象,不能延时
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
2.懒汉式(线程安全,调用效率不高,但是能延时加载):
public class Singleton {
//类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
3.静态内部类实现方式(线程安全,调用效率高,可以延时加载):
public class Singleton {
private Singleton() {}
private static class SingletonHolder{
private static final Singleton singleton = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.singleton;
}
}
如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的类构造器,其他线程都需要阻塞等待,直到活动线程执行方法完毕。
特别需要注意的是,在这种情形下,其他线程虽然会被阻塞,但如果执行方法的那条线程退出后,其他线程在唤醒之后不会再次进入/执行方法,因为在同一个类加载器下,一个类型只会被初始化一次。如果在一个类的方法中有耗时很长的操作,就可能造成多个线程阻塞,在实际应用中这种阻塞往往是隐藏的。
4.枚举类(线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用):
枚举就是在一个类里定义几个静态变量,每个变量都是这个类的实例。
public enum Singleton {
//枚举元素本身就是单例
INSTANCE;
//添加自己需要的操作
public void singletonOperation(){
}
}
5.双重检查锁 DCL
public class Singleton {
private static [volatile] Singleton instance = null;
private Singleton (){}
public static Singleton getInstance(){
if(instance == null) {
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
锁之前判断是为了优化,提高性能。锁是为了防止多线程同时申请到多个实例。
但是却有一个致命的缺陷,那就是初始化对象并不是一个原子操作,
instance = new Singleton();
为了提高性能,编译器和处理器常常会对指令进行重排序。重排序分为三种:编译器重排序,指令级并行重排序,内存系统重排序,实现优化,优化结果可能是
- 初始化 Singleton 对象;
- 把 Singleton 对象地址赋给instance变量;
也可能是这样
- 初始化一半Singleton 对象;
- 把Singleton 对象地址赋给instance变量;
- 初始化剩下的Singleton 对象;
如果是第二种情况,在多线程情况下第一个线程已经进入了instance = new Singleton (); 正在初始化,此时已经将Singleton 对象的地址赋给instance变量,但是Singleton 对象仍为初始化完毕。导致对象的某些属性为空的情况。
解决方法:instance变量加上,重排序限制,也就是volatile。
- 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
- 指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism,ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
- 内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
重排序会遵循as-if-serial与happens-before原则
volatile底层是实现内存屏障,来保证有序性的
处理器为啥要重排序?