(1)饿汉式
public class Singleton{
private static final Singleton INSTANCE = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return INSTANCE;
}
}
分析:写法简单,在类加载阶段就完成了实例化,避免了线程同步的问题;但是没有达到懒加载的效果,如果程序中一直没有使用到这个类,则会造成内存浪费。
(2)懒汉式
public class Singleton{
private static final Singleton INSTANCE;
private Singleton(){}
public static Singleton getInstance(){
if(INSTANCE == null){
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
分析:相比于上一种实现方法,达到了懒加载效果;但是这种方式只能使用在单线程环境下,一个线程进入了if (INSTANCE == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
(3)懒汉式2.0(使用同步方法保证线程安全)
public class Singleton{
private static final Singleton INSTANCE;
private Singleton(){}
public static Synchronized Singleton getInstance(){
if(INSTANCE == null){
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
分析:为解决上一种实现方案线程不安全的问题,解决方案就是做个线程同步就可以了,于是就对getInstance()方法进行了线程同步,即对getInstance()方法加同步锁,这就保证了单例的唯一性,但是缺效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步
(4)双重检查式
public class Singleton{
private static final Singleton INSTANCE;
private Singleton(){}
public static Singleton getInstance(){
if(INSTANCE == null){
Synchronized(Singleton.class){
if(INSTANCE == null){
INSTANCE = new Singleton(); ①
}
}
}
return INSTANCE;
}
}
分析:表面上来看,在执行该代码时,先判断INSTANCE 对象是否为空,为空时再进行初始化对象。即使是在多线程环境下,因为使用了Synchronized锁进行代码同步,该方法也仅仅创建一个实例对象。但是,从根本上来说,这样写还是存在一定问题的。
下面具体分析这个问题:
上述代码标号为①的代码功能是是创建实例对象,可以分解为如下伪代码步骤:
a.分配对象的内存空间
b.初始化对象
c.设置INSTANCE 指向刚分配的内存地址
其中b,c两个步骤可能会出现指令重排现象,执行顺序变成a,c,b。这样会出现的问题是:如果A线程执行了a,c两步后,线程B开始执行到b步骤,此时进入第一次 if 判断后,INSTANCE不为null,直接返回INSTANCE,但此时对象并没有被初始化,B线程就会抛出对象为初始化的异常。
对应的解决办法如下:两种实现方式
(5)不让b,c两步重排序(使用volatile关键字禁止多线程下JVM指令重排)
public class Singleton{
private static final volatile Singleton INSTANCE;
private Singleton(){}
public static Singleton getInstance(){
if(INSTANCE == null){
synchronized(Singleton.class){
if(INSTANCE == null){
INSTANCE = new Singleton(); ①
}
}
}
return INSTANCE;
}
}
(6)允许b和c进行重排序,但排序之后,不允许其他线程看到。
静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonHolder类,从而完成Singleton的实例化。 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。该方案的实质是,允许b和c进行重排序,但不允许非构造线程(此处是B线程)“看到”这个重排序。
public class Singleton{
public static class SingleHolder(){
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingleHolder.INSTANCE;
}
}
分析:上面这种实现方式虽然没有显示的使用Synchronized,但底层还是使用了Synchronized。是接住了ClassLoader类的线程安全机制,即是ClassLoader中的loadClass方法在加载类的时候使用了Synchronized,除非被重写,这个方法默认在整个加载过程中都是同步的,也就是保证了线程安全。
多个线程尝试使用CAS同时改变一个变量时,只有其中一个线程能改变变量的值,而其他线程都失败,但这些线程不会挂起,而是被通知这次竞争失败,可以再次尝试忙等待,没有线程切换和阻塞的额外消耗,但是如果一直执行不成功,会造成CPU很大的消耗。
public class Singleton {
private static final AtomicReference<Singleton> INSTANCE= new AtomicReference<>();
private Singleton(){}
public static final Singleton getInstance(){
for (;;){
Singleton current = instance.get();
if(current != null)
return current;
current = new Singleton(); //如果多个现成执行到这里的话,会有大量对象创建,有可能导致内存溢出
if(instance.compareAndSet(null,current))
return current;
}
}
}