单例模式的内部类实现

内部类实现->避免了加锁,是性能最优的实现方法

实现方法

**
 * 静态内部类的单例模式
 * 性能最优
 */
public class LazyInnerClassSingleTon  {

    /**
     * 私有化构造方法
     */
    private LazyInnerClassSingleTon(){}

    /**
     * 全局访问点
     */
    public static final LazyInnerClassSingleTon getInstance(){
        return LazyHolder.LAZY;
    }

    /**
     * 静态内部类的实现方式,避免了加锁,使用了类加载的特征:内部类比外部类先加载--懒汉式
     * 懒汉式:因为LazyHolder里面的代码要等外部调用时才执行,使用JVM底层逻辑,避免线程安全问题
     */
    private static class LazyHolder{
        private static final LazyInnerClassSingleTon LAZY = new LazyInnerClassSingleTon();
    }
}

测试代码


public class MySingletonTest {
    public static void main(String[] args) {
        new Thread(new MyExecoter()).start();
        new Thread(new MyExecoter()).start();
        System.out.println("--------main线程结束---------");
    }
    static class MyExecoter implements Runnable{

        @Override
        public void run() {
            LazyInnerClassSingleTon lazy = LazyInnerClassSingleTon.getInstance();
            System.out.println(Thread.currentThread()+":"+lazy);
        }
    }
}

结果:
运行结果

缺点1:通过反射被破坏单例

但有可能被通过反射的方式攻击,因为可以通过反射直接获取构造方法,故有可能被误用,示例:

public class MySingletonTest {
    public static void main(String[] args){
        Class<?> clzz = LazyInnerClassSingleTon.class;
        try {
            Constructor constructor = clzz.getDeclaredConstructor(null);//获取构造方法
            constructor.setAccessible(true);//授权
            Object o1 = constructor.newInstance(null);//获取实例1
            Object o2 = LazyInnerClassSingleTon.getInstance();//获取实例2
            System.out.println(o1);
            System.out.println(o2);
            System.out.println(o1==o2);//直接比较地址是否相同

        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("--------main线程结束---------");
    }
}

运行结果
结果
可以看出,通过反射得到的实例与直接调用得到的实例不相同,未能实现单例。

解决办法

修改构造方法即可,其他地方不变

/**
     * 私有化构造方法
     */
    private LazyInnerClassSingleTon(){
        //禁止使用反射创建实例
        if (LazyHolder.LAZY != null){
            throw new RuntimeException("不允许构建多个实例");
        }
    }

在构造方法中通过禁止反射,强迫用户只能通过正常调用来获取实例。
此时原来的测试代码的运行结果为报错:
在这里插入图片描述

缺点2:通过序列化被破坏单例

如果对象实现了序列化接口,则有可能通过序列化和反序列化的方式被破坏单例
实现序列化接口
通过序列化破坏单例的代码实例

public class MySingletonTest {
    public static void main(String[] args) {
        LazyInnerClassSingleTon lazy1 = null;
        LazyInnerClassSingleTon lazy2 = LazyInnerClassSingleTon.getInstance();
        FileOutputStream fos = null;

        try {
            //----------------将lazy2通过序列化写入磁盘--------------------------
            fos = new FileOutputStream("LazyInnerClassSingleTon.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(lazy2);
            oos.flush();
            oos.close();

            //----------------将从磁盘反序列化读入一个实例lazy1---------------------
            FileInputStream fis = new FileInputStream("LazyInnerClassSingleTon.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            lazy1 = (LazyInnerClassSingleTon) ois.readObject();
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println(lazy1);
        System.out.println(lazy2);
        System.out.println(lazy1==lazy2);
    }
}

结果为
运行结果
可见,已经变成了两个不同的实例,不是单例了

解决办法

在LazyInnerClassSingleTon类中重写readResolve方法即可,其余不变

/**
     * 解决序列化破坏单例的情况
     * @return
     */
    private Object readResolve(){
        return LazyHolder.LAZY;
    }

运行结果
结构

解决序列化破坏单例问题的原理:

1.从ois.readObject();开始进入看源码
在这里插入图片描述
2.继续点击readObject
在这里插入图片描述
3.在readObject方法中点击readObject0
在这里插入图片描述
4.由于是读入的文件,所以在readObject0中走到了二进制,进入readOrdinaryObject
在这里插入图片描述
5.在readOrdinaryObject里,看到这里是在判断对象是否有构造方法,如果有就新建一个实例,没有则返回空,我们的LazyInnerClassSingleTon有构造方法,故此处会新建一个实例
在这里插入图片描述
6.继续在readOrdinaryObject里往下走,会判断这个对象是否有readResolve的方法,如果有,则会调用这个方法,并用这个方法的值来代替前面新建的实例。故在这里完成了覆盖,保证了单例。
在这里插入图片描述
7.hasReadResolveMethod())的方法结果来自于
在这里插入图片描述
查找readResolveMethod的赋值
在这里插入图片描述
继续通过此方法查下去,最终来到Class.java,看到是通过反射查找到这个无参数的readResolve方法,并将此方法返回来了,所以在第7步的hasReadResolveMethod的结果为true,第6步的覆盖的过程得以执行,保证了单例。
在这里插入图片描述
此方法其实还是实例化了两次,只不过反序列化的那个示例被覆盖掉了,会被gc回收

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值