单例模式复习

创建型模式一:单例模式

学习来自狂神说

kuangstudy.com

最重点思想: 私有构造器.
这就是不让在外部调用构造器.
单例: 只有一个对象.

单例:

  1. 饿汉式
  2. 懒汉式 --> DCL懒汉式
  3. 枚举

饿汉式单例:

一上来就先 new 一个对象, 好像饿汉一样, 所以叫饿汉单例

饿汉的问题: 因为一上来就 new 出对象, 所以可能会浪费内存

// 饿汉式单例子
public class Hungry {

    // 可能会浪费空间
    private byte[] data1 = new byte[1024 * 1024];
    private byte[] data2 = new byte[1024 * 1024];
    private byte[] data3 = new byte[1024 * 1024];
    private byte[] data4 = new byte[1024 * 1024];

    private Hungry() {

    }

    private final static Hungry HUNGRY = new Hungry();

    public static Hungry getInstance() {
        return HUNGRY;
    }

}

因为不想不用就创建这个单例, 平时就放在这, 用的时候再创建

所以就出现了懒汉式单例

懒汉式单例

// 懒汉式单例
public class LazyMan {


    private LazyMan() {

    }

    private static LazyMan lazyMan;

    public static LazyMan getInstance() {
        if (lazyMan == null) {
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
}

发现问题:
单线程 确实单例OK
但如果要是多线程并发 , 就没用了
解决办法

// DCL懒汉式单例
public class LazyMan {


    private LazyMan() {
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    private static LazyMan lazyMan;

    // 双重检测锁模式的懒汉式单例 DCL懒汉式
    public static LazyMan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyMan.class){
                if (lazyMan == null) {
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }

    // 多线程并发
    public static void main(String[] args) {
        for (int i = 0; i < 50; i++) {
            new Thread(() -> {
                LazyMan.getInstance();
            }).start();
        }
    }

}

为什么要两次检测 lazyMan == null, 因为怕出现并发问题. 必须线程上锁.

简单复习一下多线程和Lambda表达式

new Thread(() -> {

		LazyMan.getInstance();

}).start();

但是这样还是有问题

因为 lazyMan = new LazyMan(); 不是一个原子性操作

不是原子性操作就有以下问题:

/**
* 1. 分配内存空间
* 2. 执行构造方法, 初始化对象
* 3. 把这个对象指向这个空间
*
* 指令可能不是 123这样运行的, 可能指令重排
* 比如运行顺序是 132
* 123
* 132 A
*     B //此时lazyMan还没有完成构造
*/

如果A中在3和2之间, B中开始运行, 这样B中认为lazyMan已经构造好了, 可实际A中只开辟了空间, 还没有初始化对象, 这时候B中会出现错误

解决办法:
避免123指令重排: 使用关键字: volatile 添加在lazyMan上

private volatile static LazyMan lazyMan;

静态内部类:

//静态内部类
public class Holder {

    private Holder() {

    }

    public static Holder getInstance() {
        return InnerClass.HOLDER;
    }

    public static class InnerClass{
        private static final Holder HOLDER = new Holder();
    }

}

但是这些 统统不安全: 因为 **反射**实在太牛逼

反射统统破解(例子: DCL懒汉)

//反射!
public static void main(String[] args) throws Exception {
    LazyMan instance1 = LazyMan.getInstance();
    Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
    declaredConstructor.setAccessible(true);
    LazyMan instance2 = declaredConstructor.newInstance();
}

与反射斗智斗勇:

在构造器中加一个判断:

private LazyMan() {
    synchronized (LazyMan.class) {
        if (lazyMan != null) {
            throw new RuntimeException("不要试图使用反射破坏异常");
        }
    }
    System.out.println(Thread.currentThread().getName() + "ok");
}

道高一尺魔高一丈:

第一个例子不用正常手段创建:

public static void main(String[] args) throws Exception {
    //LazyMan instance1 = LazyMan.getInstance();
    Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
    declaredConstructor.setAccessible(true);
    LazyMan instance1 = declaredConstructor.newInstance();
    LazyMan instance2 = declaredConstructor.newInstance();

    System.out.println(instance1);
    System.out.println(instance2);
}

仍然可以创建多个单例对象

解决上面办法:

用标志作记号, 反射也能获取标志

private static boolean haozhancc = false;

private LazyMan() {
    synchronized (LazyMan.class) {
        if (haozhancc == false) {
            haozhancc = true;
        }else {
            throw new RuntimeException("不要试图使用反射破坏异常");
        }
    }
}

解决办法的办法 (魔高一丈):

解密 找到haozhancc就可以:

public static void main(String[] args) throws Exception {
    //LazyMan instance1 = LazyMan.getInstance();
    Field traceThis = LazyMan.class.getDeclaredField("haozhancc");
    traceThis.setAccessible(true);

    Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
    declaredConstructor.setAccessible(true);
    LazyMan instance1 = declaredConstructor.newInstance();

    traceThis.set(instance1, false);

    LazyMan instance2 = declaredConstructor.newInstance();

    System.out.println(instance1);
    System.out.println(instance2);
}

想解决办法:

发现枚举:

new Instance源码中:

if ((clazz.getModifiers() & Modifier.ENUM) != 0)
    throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor;   // read volatile

枚举自带单例模式:

引出: 枚举是什么?

枚举本身也是一个Class类

public enum EnumSingle {

    INSTANCE;

    public EnumSingle getInstance(){
        return INSTANCE;
    }

}

反射不能破坏枚举单例
另外:

  1. 饿汉式 (线程安全)
  2. 懒汉式 (线程安全)
  3. 双重检查锁实现 (线程安全)
  4. 静态内部类实现 (线程安全)
  5. 枚举类实现 (线程安全)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值