【并发编程】线程安全单例

  

       个人主页五敷有你      
 系列专栏:并发编程
⛺️稳重求进,晒太阳

产生线程安全的原因

  • 1. 操作系统中,线程的调度是随机的
  • 2. 两个线程针对同一个变量进行修改
  • 3. 修改操作,不是原子性的
  • 4. 内存可见性问题

解决线程安全

使用synchronized关键字可以将代码块或方法标记为同步的,这样只有一个线程可以访问它。这可以防止多个线程同时访问共享数据。

【并发编程】线程安全单例_第1张图片

      单例模式有很多实现方法,饿汉、懒汉、静态内部类、枚举类,试分析每种实现下获取单例对象(即调用getInstance)时的线程安全,并思考注释中的问题

饿汉式:类加载就会导致该单实例对象被创建

懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

 实现1

// 问题1:为什么加 final                                          
// 问题2:如果实现了序列化接口, 还要做什么来防止反序列化破坏单例     
public final class Singleton implements Serializable {
    // 问题3:为什么设置为私有? 是否能防止反射创建新的实例?
    private Singleton() {}
    // 问题4:这样初始化是否能保证单例对象创建时的线程安全?
    private static final Singleton INSTANCE = new Singleton();
    // 问题5:为什么提供静态方法而不是直接将 INSTANCE 设置为 public, 说出你知道的理由
    public static Singleton getInstance() {
        return INSTANCE;
    }
    public Object readResolve() {
        return INSTANCE;
    }
}
  • 问题1:为什么加 final ?
    • 怕有子类,覆盖了父类的方法破坏了单例。
  • 问题2:如果实现了序列化接口, 还要做什么来防止反序列化破坏单例?
    • 创建对象不知有new的接口,以后反序列化也会创建对象,之后与单例原来的对象就是不同的对象了
    • 解决方法 加入下面这个对象
public Object readResolve(){
    return INSTANCE;
}
  • 问题3:为什么设置为私有? 是否能防止反射创建新的实例?
    • 因为public会让外部产生别的对象,就不是单例了
    • 设置私有也不能防止反射创建新的实例。
  • 问题4:这样初始化是否能保证单例对象创建时的线程安全?
    • 没有安全问题,这是在类加载阶段产生的,是线程安全的,相信JVM
  • 问题5:为什么提供静态方法而不是直接将 INSTANCE 设置为 public, 说出你知道的理由
    • 提供更好的封装性。
    • 支持泛型,用成员方法就不支持泛型了

实现2

// 问题1:枚举单例是如何限制实例个数的
// 问题2:枚举单例在创建时是否有并发问题
// 问题3:枚举单例能否被反射破坏单例
// 问题4:枚举单例能否被反序列化破坏单例
// 问题5:枚举单例属于懒汉式还是饿汉式
// 问题6:枚举单例如果希望加入一些单例创建时的初始化逻辑该如何做
enum Singleton {
    INSTANCE;
}
  • 问题1:枚举单例是如何限制实例个数的
    • 定义的枚举对象,定义有几个就有几个对象,相当于静态的成员变量
  • 问题2:枚举单例在创建时是否有并发问题
    • 没有,这是在类加载时期完成的,不会有并发问题
  • 问题3:枚举单例能否被反射破坏单例
    • 不能
  • 问题4:枚举单例能否被反序列化破坏单例
    • 枚举类默认都是实现了反序列化接口的
    • 枚举在实现的时候考虑到了这个问题,无需你来处理。
  • 问题5:枚举单例属于懒汉式还是饿汉式
    • 饿汉式
  • 问题6:枚举单例如果希望加入一些单例创建时的初始化逻辑该如何做
    • 加一个构造方法

实现3

public final class Singleton {
    private Singleton() { }
    private static Singleton INSTANCE = null;
    // 分析这里的线程安全, 并说明有什么缺点  --锁的范围有点大
    public static synchronized Singleton getInstance() {
        if( INSTANCE != null ){
            return INSTANCE;
        }
        INSTANCE = new Singleton();
        return INSTANCE;
    }
}

现4:DCL(Double Checked Lock)

public final class Singleton {
    private Singleton() { }
    // 问题1:解释为什么要加 volatile ? 防止指令重排序
    private static volatile Singleton INSTANCE = null;

    // 问题2:对比实现3, 说出这样做的意义 ---,减少了锁住的范围,性能更优越
    public static Singleton getInstance() {
        if (INSTANCE != null) {
            return INSTANCE;
        }
        synchronized (Singleton.class) {
            // 问题3:为什么还要在这里加为空判断, 之前不是判断过了吗 ---防止首次创建对象的时候多个线程并发的问题
            if (INSTANCE != null) { // t2
                return INSTANCE;
            }
            INSTANCE = new Singleton(); //加了volatile就会保证不会把构造方法排到赋值操作后面去了
            return INSTANCE;
        }
    }
}

实现5:

public final class Singleton {
    private Singleton() { }
    // 问题1:属于懒汉式还是饿汉式  ---懒汉
    private static class LazyHolder {
        static final Singleton INSTANCE = new Singleton();
    }
    // 问题2:在创建时是否有并发问题 ---jvm不用担心,他会出手
    public static Singleton getInstance() {
        return LazyHolder.INSTANCE;
    }
}

你可能感兴趣的:(并发编程,java,单例模式,开发语言,并发编程,jvm)