阅读 35

多线程下的单例模式

public class Singleton{
    private static Singleton instance;
    private Singleton{
        
    }
    public static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}
复制代码

以上便是单例模式的一般实现,本文想讨论一下单例模式在多线程中应用时的情况。

如果实例为空,可能存在两个线程同时调用getInstance方法的情况。如果发生这种情况,第一个线程会首先使用新构造器实例化单例对象,同时第二个线程也会检查单例实例是否为空,由于第一个线程还没完成单例对象的实例化操作,所以第二个线程会发现这个实例是空的,也会开始实例化单例对象。在实例化单例对象需要较长时间的情况下,这种情况是可能发生的。

为了解决这个问题,我们需要创建一个代码块来检查实例是否空线程安全。可以通过以下两种方式来实现。

方法一:向getInstance方法的声明中添加synchronized关键字以保证其线程安全:

public static synchronized Singleton getInstance()
复制代码

方法二:用synchronized代码块包装if(instance==null)条件。在这一环境中使用synchronized代码块时,需要指定一个对象来提供锁,Singleton.class对象就起这种作用。

synchronized(Singleton.class){
    if(instance == null){
        instance = new Singleton();
    }
}
复制代码

上面的实现方式能够保证线程安全,但同时带来了延迟。用来检查实例是否被创建的代码是线程同步的,也就是说此代码块在同一时刻只能被一个线程执行,但是同步锁(locking)只有在实例没被创建的情况下才起作用。如果单例实例已经被创建了,那么任何线程都能用非同步的方式获取当前的实例,而无需经过线程锁的同步过程,避免了不必要的延迟。

所以,只有在单例对象未实例化的情况下,才能在synchronized代码块前添加附加条件移动线程安全锁:

if(instance == null){
    synchronized(Singleton.class){
        if(instance == null){
            instance = new Singleton();
        }
    }
}
复制代码

以上便是所谓的同步锁单例模式的双重校验锁机制,听着很复杂,其实很简单。

还有一种单例的实现方式可以避免使用同步锁机制和检查实例是否被创建,这种方式中类只会加载一次,通过在声明时直接实例化静态成员的方式来保证一个类只有一个实例:

public class Singleton{
    private static final Singleton instance = new Singleton();
    private Singleton{
        
    }
    public static Singleton getInstance(){
        return instance;
    }
}
复制代码

微信公众号编程技术漫谈