大忙人系列设计模式_单例模式的7种创建方法,以及它们的优缺点

目录

写在文章开始

什么是单例?

单例的应用

1、饥饿式

2、懒汉式

3、双重校验式

4、静态内部类

5、枚举

6、容器类管理。

攻防

使用反射技术破解单例

防止被反射暴力破解

防止序列化暴力破解

写在末尾


写在文章开始

单例一共有如下几种写法:懒汉式(非线程安全)、懒汉式(线程安全)、饥饿式、双重校验式、枚举式、静态内部类、容器类管理。

什么是单例?

单例模式属于工厂模式的特殊情况,只是它不需要输入参数并且始终返回同一对象的引用。单例模式能够保证某一类型对象在系统中的唯一性,即某类在系统中只有一个实例。

即:在单例模式中,首先必须要有一个private访问级别的构造函数,只有这样才能确保单例不会在系统中的其他代码内被实例化。
        其次,instance成员变量和getInstance()方法必须是static修饰的。

单例的应用

1、饥饿式

饿汉式,核心关键在于“饿”,不管三七二十一,咱们先做了再说,先把对象创建起来存放在仓库(JVM内存)中,你需要的时候再来调用即可。也就是说,我们如果使用饿汉式的话,就是要在这个系统启动的时候就会去创建这个实例。

/**
 * @Author: 遊亦洋
 * @Company Name:ulting
 * @Description: 普通单例
 * @Create Time: 2019-05-27 16:22
 **/
public class Singleton {
    private static Singleton instance = new Singleton();
    public String name="Singleton";

    /**
     * 单例之所以需要一个无参的构造函数,是因为单例类是不允许其他类直接“new”的,只能通过调用getInstance方法获取当前对象
     */
    private Singleton(){}
    public static Singleton getInstance(){
        return instance;
    }
}

        不足:此方法无法做到延迟加载。因为instance是static类型的,当JVM加载单例类时,单例对象就会被建立。
        因此这个单例类在系统中其他地方还有被引用时,那么在任何地方被引用时都会实例化这个对象。

2、懒汉式(2种)

懒汉式,顾名思义“懒”,不到万不得已的时候就不会去做这个事情。也就是说,我们如果使用懒汉式的话,就是要在这个类被使用的时候才会去创建这个实例。当然,懒汉式的话也有两种方法创建:一种是“线程安全”、一种是“线程不安全的”(线程安全和不安全区别在于是否引入了synchronized ,所以另外一种不安全模式暂时不贴代码了)。

线程安全的创建方法

/**
 * @Author: 遊亦洋
 * @Company Name:ulting
 * @Description: 普通单例
 * @Create Time: 2019-05-27 16:22
 **/
public class LazySingleton {
    private LazySingleton(){
        System.out.println("延迟加载开始...");
    }

    /**
     * 先定义一个静态变量instance,并赋值为null。确保在系统加载的时候没有额外的负载。
     */
    private static LazySingleton instance = null;
    public String name = "LazySingleton";

    public static synchronized LazySingleton getInstance(){
        if (instance==null){
            instance=new LazySingleton();
        }
        return instance;
    }
}

       不足:虽然此种方法实现了延迟加载功能,但是和第一种方法相比,它引入了同步关键字synchronized;所以在多线程的环境下它的延时远远大于第一种单例模式

为了延迟加载而引入了同步关键字反而降低了系统性能,所以很得不偿失。

3、双重校验式

/**
 * @Author: 遊亦洋
 * @Company Name:ulting
 * @Description: 双重校验式单例
 * @Create Time: 2019-06-12 2:46
 **/
public class DoubleCheckSingleton {
    private DoubleCheckSingleton(){
        System.out.println("双重校验式单例执行私有构造方法");
    }

    public String name="张三";
    private static DoubleCheckSingleton doubleCheckSingleton = null;

    public static DoubleCheckSingleton getInstance(){
        //第一次判断,用户是否创建对象
        if (doubleCheckSingleton==null){
            //如果用户没有创建对象,加锁
            synchronized(DoubleCheckSingleton.class){
                //当用户获得锁以后,判断初始化是否创建对象
                if (doubleCheckSingleton==null){
                    doubleCheckSingleton = new DoubleCheckSingleton();
                }
            }
        }
        return doubleCheckSingleton;
    }
}

缺点:在单个线程中没有问题,但多个线程同时访问的时候就可能同时创建多个实例,而且这多个实例不是同一个对象,虽然后面创建的实例会覆盖先创建的实例,但是还是会存在拿到不同对象的情况. 

 优点:不调用getInstance()就不会实例化,提高效率.

4、静态内部类

/**
 * @Author: 遊亦洋
 * @Company Name:ulting
 * @Description: 内部类加载方法
 * @Create Time: 2019-05-27 16:49
 **/
public class StaticSingleton {
    private StaticSingleton(){
        System.out.println("内部类加载方法开始...");
    }
    public String name = "StaticSingleton";
    private static class SingletonHolder{
        SingletonHolder(){
            System.out.println("内部类初始化开始...");
        }
        private static StaticSingleton instance = new StaticSingleton();
    }
    public static StaticSingleton getInstance(){
        final SingletonHolder singletonHolder = new SingletonHolder();
        return SingletonHolder.instance;
    }
}

不足:虽然通过静态内部类形式解决了延迟加载的性能问题,但是外界还是可以通过反射、序列化方式进行暴力破解

优点:此方法使用内部类来维护单例的实例。只有当我们调用getInstance()方法时我们才能获取到实例对象。其他时候当我们加载StaticSingleton时就不会加载StaticSingleton实例

5、枚举式

/**
 * 当我们使用其他方式的单例的时候,容易被通过序列化、反射进行暴力破解
 * @Author: 遊亦洋
 * @Company Name:ulting
 * @Description: 枚举防止暴力破解
 * @Create Time: 2019-05-27 16:49
 **/
public enum EnumSingleton {
    INSTANCE;//INSTANCE代表一个EnumSingleton的对象
    // 枚举能够绝对有效的防止实例化多次,和防止反射和序列化破解
    public void add() {
        System.out.println("枚举的add方法...");
    }
}


private static void enumSingletonMain()throws Exception{
    EnumSingleton instance1 = EnumSingleton.INSTANCE;
    EnumSingleton instance2 = EnumSingleton.INSTANCE;
    System.out.println(instance1 == instance2);
    //测试是否能暴力破解...(下面抛出异常:Exception in thread "main" java.lang.NoSuchMethodException: com.ulting.enumsingleton.EnumSingleton.<init>())
    Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor();
    declaredConstructor.setAccessible(true);
    EnumSingleton v3 = declaredConstructor.newInstance();
    System.out.println(v3==instance1);
}

优点:枚举能够绝对有效的防止实例化多次,和防止反射和序列化破解

枚举源码:我们需要通过特殊的Java反编译器.exe工具进行反编译枚举的.class文件才能看到源码。

 

由图中第7行可以看出,当我们定义一个枚举类的时候,其实我们是继承的一个java的枚举类。

由图中27行可以看出,当我们在枚举中定义参数“INSTANCE”时,其实我们定义的是一个EnumSingleton的变量,所以我们可以在调用本例子的时候,我们可以直接通过EnumSingleton.INSTANCE获取到当前的实例。

 

6、容器类管理。

public class SingletonManager {
    private static Map<String, Object> objMap = new HashMap<String, Object>();
    public static void registerService(String key, Object instance) {
        if (!objMap.containsKey(key)) {
            objMap.put(key, instance);
        }
    }
    public static Object getService(String key) {
        if (objMap.containsKey(key)) {
            return objMap.get(key);
        }
    }
}

容器管理,就是通过一个map集合来管理我们要存储我们要实例化的单例对象。

不足:高并发情况下HashMap线程不安全,即便是换成ConcurrentHashMap,因为我们使用的是static方法,所以依旧会存在线程不安全的情况。

攻防

使用反射技术破解单例

/**
 * 通过反射的方式破解单例
 */
private static void reflectionCracke()throws Exception{
    //1、先获取实例
    final LazySingleton instance = LazySingleton.getInstance();
    //2、使用java反射初始化对象
    final Constructor<LazySingleton> declaredConstructor = LazySingleton.class.getDeclaredConstructor();
    //3、设置获取无参构造函数权限
    declaredConstructor.setAccessible(true);
    //4、执行无参构造函数
    final LazySingleton lazySingleton = declaredConstructor.newInstance();

    //执行结果不相等,说明执行了两次初始化
    System.out.println(instance==lazySingleton);
}

防止被反射暴力破解

private LazySingleton(){
    //如果要防止反射暴力破解,我们需要在这里判断对象是否被初始化,如果被初始化了直接抛出异常
    synchronized (this){
        if(instance!=null){
            throw new RuntimeException("不能多次初始化");
        }
    }
    System.out.println("延迟加载开始...");
}

防止序列化暴力破解

//返回序列化获取对象 ,保证为单例
//即如果我们要防止序列化暴力破解,只需要在单例类中添加readResolve()方法即可
public class Singleton implements Serializable{
    private Object readResolve(){
         return instance; //默认返回 instance 对象,而不是重新生成一个新的对象
    }
}

写在末尾

本文参考某蚁教育、java程序性能优化、大话设计模式

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值