详解单例模式

本文详细介绍了Java中的单例模式,包括节省资源和方便控制的优势。讨论了饿汉式和懒汉式的实现,以及它们在多线程环境下的问题。针对这些问题,提出了双重验证加锁的解决方案,并指出反射可能破坏单例的安全性。最后,通过枚举单例模式来确保线程安全和防止反射攻击。
摘要由CSDN通过智能技术生成

1.为什么要用单例模式?

单例模式是Java中最简单的设计模式,创建一个单一的类,这个类只创建自己的对象,同时确保只有一个对象被创建,并且提供获得该对象的get方法。

1.1单例模式节省公共资源

大家都要喝水,但是没必要每人家里都打一口井是吧,通常的做法是整个村里打一个井就够了,大家都从这个井里面打水喝。

对应到我们计算机里面,像日志管理、打印机、数据库连接池、应用配置。

1.2 单例模式方便控制

就像日志管理,单例模式避免了多个人同时写日志导致混乱,只能一个一个写

2.实现方式

/**饿汉式,在刚开始的时候就把对象new出来
 * @author xzk
 * @creat 16:44
 */
public class Lazy{
    //构造器私有化
    private Singleton(){};
    //定义静态的全局变量
    private static final Lazy lazy = new Lazy();

    public static Singleton getLazy(){
        return lazy;
    }
}

饿汉式存在很大的问题就是,对象在刚开始的时候就被new了出来,但是这时候并不一定会用到,造成了资源的浪费。

//懒汉式
public class LazySingleton {

    //构造器私有化
    private LazySingleton(){
    }
    //定义全局变量
    private static  LazySingleton lazySingleton;

    public static LazySingleton getLazySingleton(){
        if(lazySingleton == null){
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }

}

懒汉式从根本上避免了资源浪费的问题,什么时候需要了什么时候new对象,但是如果测试多线程就会发现,在多线程下,并不只会创建一个单对象。

//只加一个锁的懒汉式
public class LazySingleton {

    //构造器私有化
    private LazySingleton(){
    }
    //定义全局变量
    private static  LazySingleton lazySingleton;

    public static LazySingleton getLazySingleton(){
         synchronized (LazySingleton .class) {
            if (lazysingleton == null) {
                singleton = new LazySingleton();
            }

        }
        return lazySingleton;
    }

}

加锁懒汉式,对类加锁避免了多个线程同时访问单例类中的getLazySingleton方法,不会导致类被多次实例化,但对获取实例方法的代码块加锁后,线程只能等上个线程执行完了再继续执行,严重影响性能。解决办法是先验证单例是否存在

//只加一个锁的懒汉式
public class LazySingleton {

    //构造器私有化
    private LazySingleton(){
    }
    //定义全局变量
    private static volatile LazySingleton lazySingleton;

    public static LazySingleton getLazySingleton(){
      if(lazySingleton == null){
         synchronized (LazySingleton .class) {
            if (lazysingleton == null) {
                singleton = new LazySingleton();
            }

        }
      }
        return lazySingleton;
    }

}

双重验证加锁的方式只有当对象不存在时,才对类进行加锁,避免了性能的浪费。但是双重检测的模式并发下还是会存在一个问题,Jvm的指令是乱序的,正常情况下是先分配内存->调用构造器,初始化对象->构建对象指向内存,但是Jvm的指令优化问题导致先将引用对象指向内存,所以如果在线程切换的时候刚好完成第二步,那下个线程就会认为这个对象已经存在,导致该线程得到一个没有初始化的对象。可以利用volatile关键字禁止指令的重排。

3.反射带来的安全问题

public static void main(String[] args) throws Exception{
        LazySingleton lazySingleton = LazySingleton.getLazySingleton();

            Constructor<LazySingleton> declaredConstructor = LazySingleton.class.getDeclaredConstructor(null);
            declaredConstructor.setAccessible(true);
            LazySingleton lazySingleton1 = declaredConstructor.newInstance();
          System.out.println(lazySingleton == lazySingleton1);

    }

通过Java的反射可以把单例模式破坏掉,导致双重验证下的单例模式还是不安全的,这时候就需要枚举登场了

 因为反射是无法破坏枚举的单例,尝试使用反射获取无参构造器发现报错 显示枚举中不存在无参构造器,利用javap -p指令反编译enum类后发现还是有无参构造器,进一步利用jad工具反编译得到java文件如下,显然枚举类的是有参构造器。利用反射获取有参构造器后再获取实例对象,抛出异常反射不能破坏枚举的单例

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值