共享模型之内存&习题

balking 模式习题

1. 希望 doInit() 方法仅被调用一次,下面的实现是否有问题,为什么?

        答:有问题。该类的对象,在多线程环境下会存在问题。比如t1和t2线程并发执行,相继进入init方法,且都读取到initialized值为false之后,执行doInit方法。这样就不满足题目要求。

public class TestVolatile {
    volatile boolean initialized = false;
    void init() {
        if (initialized) {
            return;
        }
        doInit();
        initialized = true;
    }
    private void doInit() {
    }
}

线程安全单例习题

        单例模式有很多实现方法,饿汉、懒汉、静态内部类、枚举类,试分析每种实现下获取单例对象(即调用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. 防止他的子类重写父类方法,破坏单例。

2. 

序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

        看重点: 以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
也就是说如果你的单例实现了Serializable,反序列化出来的对象,是重新创建的对象了。

解决方法,单例类中增加readResolve()方法,可以避免实例重复。

        可以通过查看readObject()源码,看看是如何反序列化创建对象的,这个方法创建完对象之后会通过反射机制判断类中是否有readResolve()方法,如果有readResolve()方法,会通过反射机制调用这个方法。所以当你在单例类中写上readResolve()方法,是能够保证得到的同一个单例的,能够保证单例的全局唯一性。

        补充: 但是避免不了序列化重复创建对象,实际上我们这种写法只是将反序列化创建的对象覆盖掉了,在执行过程中JVM还是创建了新的对象。

3. 防止其他类创建对象,保证单例。不能,反射可以得到constructor对象,设置构造器对象的setAccessible为true,暴力反射,调用构造方法,创建新的实例。

4.没有,静态成员变量的初始化是在类加载阶段完成的,类加载阶段由jvm保证代码的线程安全性。

5.用方法可以提供更好的封装性,可以实现懒惰的初始化,还可以提供泛型的支持。对创建单例变量有更好的控制。

实现2


// 问题1:枚举单例是如何限制实例个数的
// 问题2:枚举单例在创建时是否有并发问题
// 问题3:枚举单例能否被反射破坏单例
// 问题4:枚举单例能否被反序列化破坏单例
// 问题5:枚举单例属于懒汉式还是饿汉式
// 问题6:枚举单例如果希望加入一些单例创建时的初始化逻辑该如何做
enum Singleton {
    INSTANCE;
}

2.1 枚举单例是如何限制实例个数的

由枚举控制,是单实例的。
2.2 枚举单例在创建时是否有并发问题

没有。因为它也是静态成员变量。
2.3 枚举能否被反射破坏单例

不能用反射破坏。拿不到构造方法,不能实现反射。
2.4 枚举单例能否被反序列化破坏单例

枚举在实现的时候就继承了序列化的接口,不能被破坏。
2.5枚举单例属于懒汉式还是饿汉式

饿汉式的,在类加载的时候就创建单例了。
2.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;
    }
}

        将锁加在了Singleton.class,可以保证线程安全。同步锁的范围太大了,在上述地方会出现效率低下,第一次的访问的时候,通过同步锁创建对象,但是对象创建了之后,可以不通过同步锁访问了。

实现4:DCL

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();
            return INSTANCE;
        }
    }
}

4.1 为什么要加volatile?

         synchronized代码块里的指令可能会重排序,如INSTANCE = new Singleton();这行代码,详情看这篇博文。在多线程环境下,读线程里调用getInstance(),判断INSTANCE不为空,可能会拿到未初始化完的对象。

-----------------------------------------------------------------------------------------------------------------------
4.2 对比实现3,说出这样做的意义

        第一次调用的话,会继续创建对象,但是第二、第三次调用,可以直接判断,不用再通过同步代码了,提升了效率。

-----------------------------------------------------------------------------------------------------------------------
4.3 为什么还要在同步代码块中加是否为空的判断

        为了防止多次创建对象。第一个进入的线程已经创建成功单例,退出代码块。第二个线程从entrySet下来,获取锁,如果不进行判断,那么就还会创建一个对象。因此说如果有两个线程相继进到代码块时,可以防止多次创建对象。

实现5:

public final class Singleton {
    private Singleton() { }
    // 问题1:属于懒汉式还是饿汉式
    private static class LazyHolder {
        static final Singleton INSTANCE = new Singleton();
    }
    // 问题2:在创建时是否有并发问题
    public static Singleton getInstance() {
        return LazyHolder.INSTANCE;
    }
}

        1. 懒汉式,类加载也是懒汉式的,只有用到才会加载。利用静态内部类特点实现延迟加载。效率高。

       2.  静态变量的赋值操作,是由jvm保证线程安全性。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值