玩转单例模式,最简单理解单例模式(看代码带你理解设计模式)

什么是单例模式(Singleton Pattern)

单例模式(Singleton Pattern)是比较简单容易理解的设计模式之一,这种设计模式是属于创建型模式,这种设计模式涉及到一个单一的类型,该类负责自己创建自己的对象,同时确保只有一个对象被创建,这个类需要对外提供一个访问获取对象的方法,可以直接访问,不需要实例化该类的对象

注意:

  • 单例类只能有一个实例
  • 单例类必须自己创建自己的唯一实例
  • 单例类必须给给所有其他对象提供这一实例
  • 构造方法要私有化,不能提供外界访问

单例模式实现的方式有多,本文就简单介绍在保证线程安全情况下常用的3种方式

单例应用场景

  • Windows系统的任务管理器。
  • Windows系统的回收站。
  • 操作系统的文件系统,一个操作系统只能有一个文件系统。
  • 数据库连接池的设计与实现。
  • 多线程的线程池设计与实现。
  • Spring中创建的Bean实例默认都是单例。
  • Java-Web中,一个Servlet类只有一个实例。 等等.

单例模式的实现

饿汉式

描述:这种方式比较常用,但容易生成垃圾对象
优点:没有加锁,执行效率会提高
缺点:类加载时候就初始化了,浪费内存,没有懒加载 lazy loading 的效果
上代码:

public class Hungry {

    //浪费资源
    private Byte[] byte1 = new Byte[1024 * 1024];
    private Byte[] byte2 = new Byte[1024 * 1024];
    private Byte[] byte3 = new Byte[1024 * 1024];
    private Byte[] byte4 = new Byte[1024 * 1024];

    private Hungry() {

    }

    private final static Hungry hungry = new Hungry();

    public static Hungry getInstance() {
        return hungry;
    }


}

懒汉式

描述:这里已经是线程安全的单例了, 也可以加入自己的密钥相关,去判断是不是单例 , 但是这种也可以用反射破坏. 所以最后面给大家介绍一下枚举!

懒汉式这种方式具备很好的 lazy loading,能过在多线程种很好的工作,但是,效率很低,99%情况下不需要通过
优点:第一次调用才初始化,避免了内存浪费,具有懒加载的效果
缺点:必须加锁 synchronized 才能过保证单例,但是加锁会影响效率 , 反射可以破坏单例结构,可以看测试代码,大家可以跑一下
注意: 这里的new LazeMan() 创建对象的时候不是原子性的.

为什么要加volatile关键字?
   1.分配内存空间
   2.构造方法,初始化对象
   3.指向内存地址
   *
   * cpu执行可能123或者132
   * 有可能下个线程进来的时候,会直接判断 lazeMan==null 这个时候
   * 内存地址已经存在了,但是对象还未初始化完成,所以对lazeMan要加 volatile;

code:

public class LazeMan {


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

    //volatile 是因为其本身包含“禁止指令重排序”的语义,
    private static volatile LazeMan lazeMan;

    public static LazeMan getInstance() {
        //加锁操作
        if (lazeMan == null) {
            synchronized (LazeMan.class) {
                if (lazeMan == null) {
                    /**
                     * new LazeMan()过程不是一个原子操作
                     * 1.分配内存空间
                     * 2.构造方法,初始化对象
                     * 3.指向内存地址
                     *
                     * cpu执行可能123或者132
                     * 有可能下个线程进来的时候,会直接判断 lazeMan==null 这个时候
                     * 内存地址已经存在了,但是对象还未初始化完成,所以对lazeMan要加 volatile;
                     */
                    lazeMan = new LazeMan();
                }
            }
        }
        return lazeMan;
    }

    public static void main(String[] args) throws Exception {
        //测试多线程
//        for (int i = 0; i < 10000000; i++) {
//            new Thread(()->{
//                LazeMan.getInstance();
//            }).start();
//        }

        //反射可以破环构造方法私有
        LazeMan instance = getInstance();
        System.out.println(instance);
        Constructor<LazeMan> declaredConstructor = LazeMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazeMan lazeMan1 = declaredConstructor.newInstance();
        System.out.println(lazeMan1);
    }

}

枚举单例

重点: 提问: 枚举里面构造方式是不是私有的 ?
那怎么知道枚举里面是有参还是无参构造呢?打开编译的文件发现里面有一个无参构造,但是代码跑出来报错,说明IDEA骗了我们,然后用jad反编译工具把class文件反编译后发现,里面是一个有参构造,而且还带了两个参数

枚举源码:
在这里插入图片描述

以下是测试代码, 反射不可已破坏

public enum Holder {

    INSTANCE;

    public Holder getInstance() {
        return INSTANCE;
    }
}

class Test {
    public static void main(String[] args) throws Exception {
        Holder instance = Holder.INSTANCE;
        System.out.println(instance);
        /**
         *  1.反射尝试破环单例, 这里枚举里面显示没有空参构造方法,
         *  Holder.class.getDeclaredConstructor(null);
         *
         *  2. 报错: 明明有空参构造方法啊,源码显示的也有,但是报错
         *  Exception in thread "main" java.lang.NoSuchMethodException: onecrm.micro.membership.provider.Holder.<init>()
         * 	at java.lang.Class.getConstructor0(Class.java:3082)
         * 	at java.lang.Class.getDeclaredConstructor(Class.java:2178)
         * 	at onecrm.micro.membership.provider.Test.main(Holder.java:33)
         *
         *  3. 用jad反编译后发现里面是   (String.class, int.class) 参数,如果传null
         *  报错: 不能反射式创建enum对象, 所以枚举是天然的单例
         *  Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
         * 	at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
         * 	at onecrm.micro.membership.provider.Test.main(Holder.java:27)
         */
        Constructor<Holder> declaredConstructor = Holder.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        Holder holder = declaredConstructor.newInstance();
        System.out.println(holder);
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值