四种线程安全的单例模式写法

单例模式(Singleton Pattern)作为Java设计模式之一,保证一个类仅有一个实例,并提供一个访问它的全局访问点。节省系统资源,也适用于需要全局实例的场景。本文中介绍的四种单例模式皆为线程安全的写法。

1. 双重检查

public class MySingletonDoubleCheck {

    private static volatile MySingletonDoubleCheck singleton;

    private MySingletonDoubleCheck(){

    }

    private static MySingletonDoubleCheck getInstence(){
        if (singleton == null){
            synchronized (MySingletonDoubleCheck.class){
                if (singleton == null){
                    singleton = new MySingletonDoubleCheck();
                    return singleton;
                }
            }
        }
        return singleton;
    }
}

主要思想

定义了私有的静态成员变量,volatile关键字可以禁止指令重排序,同时写了一个私有的空的无参构造方法防止强行使用new实例出一个变量。synchronized (MySingletonDoubleCheck.class) 保证了当同时有两个线程想要实例化singleton变量时,只会让第一个线程实例化出变量,等到第二个线程进入同步代码块时singleton变量已经不为null了。

volatile关键字的作用

volatile关键字的作用:在new出一个新对象的过程中,计算机其实将其分成了3个步骤,第一步首先给singleton分配内存,第二步随后调用构造方法初始化,第三步最后将singleton对象指向分配的内存空间,此时singleton才不为null。看起来顺理成章,但是计算机为了提高效率,在不影响结果的情况下,可能会改变这三个步骤的顺序,也就是说,可能会先执行第三步,再执行第二步。注意,执行完第三步之后,singleton变量就已经不是null了,但实际上它还没有完成初始化,此时如果有第二个线程也想要得到单例对象,在第一次判断singleton是否为null时就将尚未初始化的singleton返回,此时返回的singleton是不完整的。

优点

延迟加载,效率高。

缺点

代码稍稍复杂了一点,需要对锁有一定理解,同时volatile也会影响一点点性能,但无伤大雅。

2. 饿汉式(静态常量)

public class Singleton {
    
    private static final Singleton INSTANCE = new SingletonSample();

    private Singleton(){

    }

    public static Singleton getInstance(){
        return INSTANCE;
    }
}

主要思想

采用静态常量的方式实现单例,十分简单,但依旧不能忘了写一个私有无参构造方法。

优点

写法简单。

缺点

在类装载时就被实例化,没有达到懒加载(Lazy Loadind)的效果,有可能造成资源浪费,比如从始至终都未使用过这个实例。

3. 静态内部类

public class SingletonStatic {

    private SingletonStatic(){

    }

    private static class SingletonInstance{
        private static final SingletonStatic SINGLETON_STATIC = new SingletonStatic();
    }

    public static SingletonStatic getInstance(){
        return SingletonInstance.SINGLETON_STATIC;
    }
}

主要思想

采用静态内部类的形式,思想与饿汉式类似,但静态内部类在类加载时不会被实例化,只有在第一次调用getInstance()方法时才会加载,类的静态属性只会在第一次加载类的时候初始化,所以JVM保证了线程的安全性,在类进行初始化时,别的线程无法干扰。

优点

延迟加载,效率高。

缺点

没有什么缺点。

4. 枚举+接口

  1. 以下为自定义接口:
public interface MySingleton {

    void doSomething();
}

  1. 利用枚举实现单例模式:
public enum Singleton implements MySingleton {
   
    INSTANCE {
        @Override
        public void doSomething() {
            System.out.println("It is my singleton");
        }
    };

    public static Singleton getInstance(){
        return Singleton.INSTANCE;
    }

}

主要思想

借助JDK1.5中添加的枚举来实现单例模式。

优点

不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。

缺点

代码较其他三种比较怪异。