JAVA并发编程(三)-单例模式

Scroll Down

JAVA并发编程(三)-单例模式

单例(singleton)模式需要实现的效果是保持一个类有且仅有一个实例,出于性能考虑,一般都会使用懒加载的模式,只有在使用到的时候才会去创建实例.

单线程版本的单例模式

public class SingleThreadSingleton {
    private static SingleThreadSingleton singleThreadSingleton = null;
    private SingleThreadSingleton(){}

    public static SingleThreadSingleton getInstance() {
        if (singleThreadSingleton == null) {
            return new SingleThreadSingleton();
        }
        return singleThreadSingleton;
    }
}

在多线程的环境下,这种写法不能生效,因为有一个if语句形成了check-then-act操作,它不是一个原子操作,可能线程T1执行new SingleThreadSingleton() 的时候,线程T2在执行if判断,尽管T1此时已经在创建一个实例了,但是T2判断还是为null,需要创建一个新的实例,导致多个实例被创建,不是正确的单例模式

简单加锁的单例模式

public class SingleMulThreadLockThread {
    private static SingleMulThreadLockThread instance = null;
    private SingleMulThreadLockThread(){}
    public static SingleMulThreadLockThread getInstance(){
        synchronized (SingleMulThreadLockThread.class) {
            if (instance == null) {
                return new SingleMulThreadLockThread();
            }
            return instance;
        }
    }
}

这种方法是简单而又可靠的,但是每次获取实例时,都需要申请锁,导致了开销增大

错误的双重检查锁定的单例模式

public class SingleMulThreadDCLThread {
    private static SingleMulThreadDCLThread instance = null;
    private SingleMulThreadDCLThread(){}
    public static SingleMulThreadDCLThread getInstance(){
        if (null == instance){//语句①
            synchronized (SingleMulThreadDCLThread.class) {
                if (null == instance) {//语句②
                    return new SingleMulThreadDCLThread();//语句③
                }
            }
        }
        return instance;
    }
}

这种方法看似很好,首先判断instance是否是null,如果是的话,再去获取锁,假如T1执行到语句①,发现是null,而此时线程T2执行到语句③,此时线程T1根据锁的原子性与可见性来看,执行到语句②时发现已经不为null了,则可以直接返回.避免了创建多个实例.但是这种写法还存在问题,在synchronized的代码块中,代码是具有原子性,可见性,有序性的,但是临界区内的代码也是可以重排序的,语句③可能会被重排序成

objRef = allocate(SingleMulThreadDCLThread.class);//申请空间,正常顺序①
instance = objRef;//将对象写入共享变量,正常顺序③
invokeConstructor(objRef);//将对象初始化,正常顺序②

但是在语句①处,可能会存在这种情况.导致我们在第一次检查时,发现instance并不等于null,但是该变量引用的对象并没有完全初始化,导致返回的实例是错误的.

正确的双重检查锁定的单例模式

public class SingleMulThreadDCLThread {
    private static volatile SingleMulThreadDCLThread instance = null;
    private SingleMulThreadDCLThread(){}
    public static SingleMulThreadDCLThread getInstance(){
        if (null == instance){
            synchronized (SingleMulThreadDCLThread.class) {
                if (null == instance) {
                    return new SingleMulThreadDCLThread();
                }
            }
        }
        return instance;
    }
}

只需要将instance变量使用volatile关键词修饰即可,这个保证处理器不会按照错误的顺序来初始化对象,使线程获取到的对象一定是初始化完毕的.

基于静态内部类实现的单例模式

public class StaticSingleton {
    private StaticSingleton(){}
    static class InstanceHolder{
        final static StaticSingleton INSTANCE = new StaticSingleton();
    }

    public static StaticSingleton getInstance() {
        return InstanceHolder.INSTANCE;
    }
}

因为静态变量首次被访问时会触发虚拟机对类进行初始化,该类的静态变量就可以为初始值而非默认值,因此静态方法getInstance()被调用时,java虚拟机会初始化静态内部类InstanceHolder,使得StaticSingleton的变量被创建,而类的静态变量只会被创建一次,因此StaticSingleton也只会有一个实例.

枚举类型的单例模式实现

public enum SingletonEnum {
    INSTANCE;
    SingletonEnum(){}
    public void  someService(){
        System.out.println("some service");
    }
}

枚举类型SingletonEnum相当于一个单例类,而字段INSTANCE是该类的唯一实例,而且这个实例只有在初次引用时才会被初始化.