单例模式

思考单例模式

单例就是确保一个类在全局只有一个实例

1.单例的本质

单例模式的本质:控制实例数目

单例模式是为了控制在运行期间,某些类的实例数目只能有一个。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

2.何时选用单例

建议在如下情况时,选用单例模式。

当需要控制一个类的实例只能有一个,而且客户只能从一个全局访问点访问它时,可以选用单例模式。

比如:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、Session工厂等)

在开发中,很多时候有一些对象其实我们只需要一个,例如:线程池(threadpool)、缓存(cache)、默认设置、注册表(registry)、日志对象等等,这个时候把它设计为单例模式是最好的选择。

例:读取配置的类,代码中有很多地方需要读取配置,就要new很多次这个类,也就是频繁的创建和销毁;java读取配置文件是读取到内存中,如果配置文件很大,加载多次非常浪费内存空间,所以使用单例读取

单例模式的好处:

它能避免实例对象的重复创建,不仅可以减少每次创建对象的时间开销,还可以节约内存空间;还能够避免由于操作多个实例导致的逻辑错误。

3.单例模式的结构

在这里插入图片描述

  • Singleton:负责创建Singleton类自己的唯一实例,并提供一个getInstance的方法,让外部来访问这个类的唯一实例。
4.实现
饿汉式(不推荐)
//饿汉式
//如果没有使用,也实例化了对象,浪费内存
public class HungryMan {

    private static HungryMan hungryMan=new HungryMan();

    private HungryMan(){
    }

    public static HungryMan getInstance(){
        return hungryMan;
    }
}
懒汉式(不推荐)
//饿汉式
//延迟加载,不会浪费内存,但出现了并发问题
public class LazyMan {

    private static LazyMan lazyMan;

    private LazyMan(){
    }

    public static LazyMan getInstance(){
        return lazyMan=new LazyMan();
    }
}
双重校验锁(推荐)
//双重校验锁(DCL懒汉)
//解决了并发问题,但可通过反射和反序列化进行更改。
public class DCLLazyMan {

    private volatile static DCLLazyMan dclLazyMan;

    private LazyMan(){
    }
    
    public static DCLLazyMan getInstance(){
        if (dclLazyMan==null){
            synchronized (DCLLazyMan.class){
                if (dclLazyMan==null){
                    dclLazyMan=new DCLLazyMan(); //不是原子性操作
                    /**
                     * 实例化对象步骤:
                     * 1.给对象分配内存空间
                     * 2.执行构造器,实例化对象
                     * 3.把这个对象指向分配的空间
                     *
                     * 正常执行顺序是:123
                     * 但是为了提高效率,可能会发生指令重排,执行顺序:132
                     * 这时候另一个线程拿到了执行完13操作的对象,就直接返回了,但实际上这个对象还未实例化
                     * 所以必须保证这个对象是原子性操作
                     */
                }
            }
        }

        return dclLazyMan;
    }
}
静态内部类(推荐)

通过类加载器来保证对象创建的线程安全和懒加载。这种方式Singleton类被装载了,instance不会被立马初始化,因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,发现instance还没初始化,才会显示装载SingletonHolder类加载instance,延迟加载

//静态内部类
public class Singleton{

    private Singleton(){
    }

    public static class SingletonHolder{
        private static StaticInner instance=new StaticInner();
    }

    public static SingletonHolder getInstance(){
        return InnerClass.instance;
    }

}

以上四种都可以被反射破解!
例:

@Test
void contextLoads() throws Exception {

    System.out.println(DCLLazyMan.getInstance());
    Constructor<DCLLazyMan> declaredConstructor = DCLLazyMan.class.getDeclaredConstructor(null);
    declaredConstructor.setAccessible(true);
    System.out.println(declaredConstructor.newInstance());

}
枚举式(最佳实践)
//枚举式
//解决了只有在使用时才进行实例化单例,线程安全,同时不能够被反序列化,以及利用反射进行破坏。
public enum EnumSingle {

    INSTANCE;

    public static EnumSingle getInstance(){
        return INSTANCE;
    }
}

枚举式不会被反射破坏!

@Test
void contextLoads() throws Exception {

    System.out.println(EnumSingle.getInstance());
    Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
    declaredConstructor.setAccessible(true);
    System.out.println(declaredConstructor.newInstance());

}

在这里插入图片描述
原因:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值