并发编程

线程安全的单例模式

2019-08-20  本文已影响12人  xiaolyuh

懒汉式

加方法锁

public class Singleton {
    private static Singleton singleton = null;

    private Singleton() {
    }

    public static synchronized Singleton getInstance() {
        if (Objects.isNull(singleton)) {
            singleton = new Singleton();
        }
        return singleton;
    }
}
  1. 直接在 getInstance() 方法加锁,但是加锁的范围太大,性能低下

双重检查锁定

public class Singleton {
    private static volatile Singleton singleton = null;

    private Singleton() {
    }

    public static Singleton getInstance() {
        // 1
        if (Objects.isNull(singleton)) {
            synchronized(Singleton.class) {
                if (Objects.isNull(singleton)) {
                  // 2  
                  singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

对象需要加volatile 关键字,主要是防止指令重排序。singleton = new Singleton();方法在执行的时候有三个指令:

memory = allocate();  // 1:分配对象的内存空间
ctorInstance(memory); // 2:初始化对象
instance = memory;  // 3:设置instance指向刚分配的内存地址

当线程A获取到锁,执行初始化的时候发生了指令重排,1->3->2。当2还没有被执行时,线程B执行到代码标记1的位置,这时判断到对象不为空,直接返回该对象,但是这个时候该对象可能还并没有完成初始化,导致线程B在执行过程中抛错。

静态内部类

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

    private Singleton() {
    }

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

既实现了线程安全,又避免了同步带来的性能影响。JVM在类的初始化阶段(即在Class被加载后,且被线程使用之前),会执行类的初始化。在 执行类的初始化期间,JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化。使用这种方式,我们是允许new Singleton();过程发生指令重排的。

使用枚举的形式

public class EnumSingleton {
    private EnumSingleton() {
    }

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

    private enum Singleton {
        INSTANCE(new EnumSingleton());
        private EnumSingleton singleton;

        //JVM会保证此方法绝对只调用一次
        Singleton(EnumSingleton singleton) {
            this.singleton = singleton;
        }

        public EnumSingleton getInstance() {
            return singleton;
        }
    }
}

JVM会保证枚举类构造方法绝对只调用一次,所以保证了对象实例的唯一性

饿汉式

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

    private Singleton() {
    }

    public static Singleton getInstance() {
        return singleton;
    }
}
上一篇 下一篇

猜你喜欢

热点阅读