静态内部类实现单例模式《3》

  1. 介绍之前先简单的说一下内部类:
    无论是静态内部类还是非静态内部类,在类初始化的时候都不会被加载 ,其实就是为了解决下面B的情况

B.饿汉模式获取全局属性会进行类的初始化

饿汉模式的详细介绍

下面重复介绍一下带有全局属性的饿汉模式

 /**
 * xiyou-todo B恶汉模式
 * 1. 如果在恶汉单例模式里面添加一个它的全局属性
 * 2. 如果想获取它的yourClass 可以调用直接调用yourClass
 * 3. 但是这样对象会初始化(虽然只会初始化一次),构造方法初始化,我不想让类初始化怎么办
 * 4
 */
public class EHanMonitor {

    public static String yourClass = "恶汉模式,通信工程1班";

    private static EHanMonitor classMonitor = new EHanMonitor();

    private EHanMonitor() {
        System.out.println("恶汉模式:构造方法初始化,只会初始化一次");
    }

    public static EHanMonitor getClassMonitor() {
        System.out.println("恶汉模式,获取对象" + classMonitor);
        return classMonitor;
    }

    public static void main(String[] args) {
        System.out.println(EHanMonitor.yourClass );
        System.out.println("上面是只调用单例模式的属性,但是也会进行类的初始化");
        System.out.println(EHanMonitor.yourClass + getClassMonitor());
        System.out.println(EHanMonitor.yourClass + EHanMonitor.getClassMonitor());
    }
}

A:静态内部类实现单例模式(简单版本)

/**
 * @author xiyou
 * 静态内部类实现单例模式
 * 看似近似完美,但是其实还是有问题的,比如反射破坏单例
 */
public class InnerClassSingleton {

    private InnerClassSingleton() {

    }

    private static final class LazyHolder {
        private static final InnerClassSingleton INNER_INSTANCE = new InnerClassSingleton();
    }

    public static final InnerClassSingleton getInstance() {
        return LazyHolder.INNER_INSTANCE;
    }

看似比懒汉模式、饿汉模式、DDL(volatile)模式都要好,但是有没有缺点呢?

有:反射可以破坏单例

A1 但是上面的静态内部类真的是保证单例吗?

答案:虽然线程安全,但是反射可以破坏单例模式


    public static void main(String[] args) {
        Object instance = InnerClassSingleton.getInstance();
        System.out.println(instance);
        //通过非正常手段,反射来破坏单例
        Class<?> oneClass = InnerClassSingleton.class;
        try {
            Constructor c = oneClass.getDeclaredConstructor();
            c.setAccessible(true);
            Object instance1 = c.newInstance();
            Object instance2 = c.newInstance();
            Object instance3 = c.newInstance();
            System.out.println(instance1);
            System.out.println(instance2);
            System.out.println(instance3);
            System.out.println(c);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 结果:发现已经破坏单例模式了
     * cn.net.health.tools.design.single.lazy.InnerClassSingleton@783e6358
     * cn.net.health.tools.design.single.lazy.InnerClassSingleton@17550481
     * cn.net.health.tools.design.single.lazy.InnerClassSingleton@735f7ae5
     * cn.net.health.tools.design.single.lazy.InnerClassSingleton@180bc464
     * private cn.net.health.tools.design.single.lazy.InnerClassSingleton()
     */
A2 怎么保证反射也是单例模式?
import java.lang.reflect.Constructor;

/**
 * @author xiyou
 * 在构造函数里面判断
 */
public class InnerClassSingleton2 {
    private InnerClassSingleton2() {
        if (LazyHolder.instance != null) {
            throw new RuntimeException("不允许反射创建实例");
        }
    }

    private static final class LazyHolder {
        private static final InnerClassSingleton2 instance = new InnerClassSingleton2();
    }

    public static final InnerClassSingleton2 getInstance() {
        return InnerClassSingleton2.LazyHolder.instance;
    }
    public static void main(String[] args) {
        Object instance = InnerClassSingleton2.getInstance();
        System.out.println(instance);
        //通过非正常手段,反射来破坏单例
        Class<?> oneClass = InnerClassSingleton2.class;
        try {
            Constructor c = oneClass.getDeclaredConstructor();
            c.setAccessible(true);
            Object instance1 = c.newInstance();
            Object instance2 = c.newInstance();
            Object instance3 = c.newInstance();
            System.out.println(instance1);
            System.out.println(instance2);
            System.out.println(instance3);
            System.out.println(c);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

结果:虽然可以抵制反射创建实例,但是构造方法里面抛出异常,有点诡异,那怎么办呢?

cn.net.health.tools.design.single.lazy.InnerClassSingleton2@783e6358
java.lang.reflect.InvocationTargetException
Caused by: java.lang.RuntimeException: 不允许反射创建实例

枚举可以解决线程安全和反射问题

A3 枚举单例可以防止反射破坏单例

C.静态内部类实现单例模式(带有静态属性)

public class StaticPropertyHungry {
    /**
     * todo-xiyou 饿汉模式
     * 1. 如果在饿汉单例模式里面添加一个它的全局属性
     * 2. 如果想获取它的yourClass 可以调用直接调用yourClass
     * 3. 但是这样对象会使当前对象初始化(构造方法初始化,虽然指挥初始化一次),但我还是不想让类初始化怎么办
     * 4.
     */
    public static final String YOUR_PROPERTY = "xiyou";


    private static StaticPropertyHungry classMonitor = new StaticPropertyHungry();

    private StaticPropertyHungry() {
        System.out.println("饿汉模式:构造方法初始化,只会初始化一次" + YOUR_PROPERTY);
    }

    public static StaticPropertyHungry getClassMonitor() {
        System.out.println("饿汉模式,获取对象" + classMonitor);
        return classMonitor;
    }

    public static void main(String[] args) {
        System.out.println(StaticPropertyHungry.YOUR_PROPERTY);
        System.out.println(StaticPropertyHungry.YOUR_PROPERTY);
        System.out.println("上面是只调用单例模式的属性,但是也会进行类的初始化");
        System.out.println(StaticPropertyHungry.YOUR_PROPERTY + getClassMonitor());
        System.out.println(StaticPropertyHungry.YOUR_PROPERTY + StaticPropertyHungry.getClassMonitor());
    }
}

结果:

饿汉模式:构造方法初始化,只会初始化一次xiyou
xiyou
xiyou
上面是只调用单例模式的属性,但是也会进行类的初始化
饿汉模式,获取对象cn.net.health.tools.design.singleton.StaticPropertyHungry@783e6358
xiyoucn.net.health.tools.design.singleton.StaticPropertyHungry@783e6358
饿汉模式,获取对象cn.net.health.tools.design.singleton.StaticPropertyHungry@783e6358
xiyoucn.net.health.tools.design.singleton.StaticPropertyHungry@783e6358

1. 为什么静态内部类能保证单例?

我们再回头看下getInstance()方法,调用的是MonitorCreator.classMonitor,
取的是SingleTonHoler里的INSTANCE对象,
跟上面那个DCL方法不同的是,getInstance()方法并没有多次去new对象,
故不管多少个线程去调用getInstance()方法,取的都是同一个INSTANCE对象,而不用去重新创建。
当getInstance()方法被调用时,SingleTonHoler才在SingleTon的运行时常量池里,把符号引用替换为直接引用,
这时静态对象INSTANCE也真正被创建,然后再被getInstance()方法返回出去,这点同饿汉模式。

2. 那么,是不是可以说静态内部类单例就是最完美的单例模式了呢?

其实不然,静态内部类也有着一个致命的缺点,就是传参的问题,由于是静态内部类的形式去创建单例的,
故外部无法传递参数进去,例如Context这种参数,
所以,我们创建单例时,可以在静态内部类与DCL(双重双重锁懒汉模式(Double Check Lock))模式里自己斟酌。双重校验的volatile懒汉单例模式

3. 静态内部类实现的单例模式为什么是线程安全的

虚拟机会保证一个类的()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕。如果在一个类的()方法中有耗时很长的操作,就可能造成多个进程阻塞(需要注意的是,其他线程虽然会被阻塞,但如果执行()方法后,其他线程唤醒之后不会再次进入()方法。同一个加载器下,一个类型只会初始化一次。),在实际应用中,这种阻塞往往是很隐蔽的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值