💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
设计模式中我们接触的最多的可能要算单例模式了,只要我们想一个类只有一个实例存在,我们就会考虑使用单例模式,对于一个刚接触编程不久的同学来说如何使用好单例可能还是有一定的困难的,今天就来告诉大家如何使用好单例这种设计模式。 其实单例模式可以分为5中,一种是懒汉式的,一种饥饿式,一种静态内部类的形式,一种枚举类的形式(推荐使用),双重校验锁的形式。 对于一般的同学些单例,可能只是考虑把构造方法私有化,没有考虑多线程的情况,一般会这样写: ~~~ public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (null == singleton) { singleton = new Singleton(); } return singleton; } } ~~~ 这是懒汉式的写法,不过这样有一个问题就是没有考虑到同步,可能你会说,同步还不简单嘛,直接在getInstance方法中加synchronized,可是这样写有一个性能的问题,因为getInstance方法我们经常会调用,而如果这个方法又是同步的,显然会有性能问题,那么如何解决这个问题呢?可能你想到了只在创建实例的时候加synchronized关键字,于是你会这么写: ~~~ public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { synchronized (Singleton.class) { if (null == singleton) { singleton = new Singleton(); } return singleton; } } } ~~~ 但是这样写依然存在一个性能问题,就是你每次判断它是否为空的时候都要执行同步代码块,如果解决这个问题呢?这就形成了我们的双重校验锁的雏形,我们判断两次是否为空,第二次不为空的时候再去创建实例,这样就解决了性能的问题: ~~~ public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (null == singleton) { synchronized (Singleton.class) { if (null == singleton) singleton = new Singleton(); } } return singleton; } } ~~~ 但是这样写会存在一个多处理器创建的时候的问题,我们知道一个类被创建的时候是需要一定时间的,cup1判断变量为空,然后他去实例化这个类,可是在实例的过程中还没有完成的时候,cup2需要使用这个类,它去判断的时候发现这个类已经被cup1实例化了,于是它就直接使用,但是cup1实例化还没有完成,这个类还是一个半成品,这个时候cup2使用必定会出现一些莫名其妙的问题,如何解决这个问题呢?好在有一个关键字volatile,这个就是用来解决多处理器变量共享的问题,于是一个完美的双重校验锁应该这样写: ~~~ public class Singleton { private volatile static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (null == singleton) { synchronized (Singleton.class) { if (null == singleton) singleton = new Singleton(); } } return singleton; } } ~~~ 上面说的懒汉式的写法,饥汉式因为本身就不存在多线程的问题,所以不必要考虑多线程,一个标准的写法如下: ~~~ public class Singleton { private static Singleton singleton=new Singleton(); private Singleton() { } public static Singleton getInstance() { return singleton; } } ~~~ 我们知道static修饰的全局变量在类加载的时候就被初始化了,所以不存在多线程的问题,但是这样也存在一个问题,就是有时候我们在类被加载的时候不想实例化这个变量,我们需要在getInstance的时候再去创建变量,可见饿汉式是做不到的,这也就是饿汉式的缺点。为了解决这个问题,就出现了静态内部类的形式,这种形式很轻松的解决了这个问题,写法如下: ~~~ public class Singleton { private Singleton() { } public static Singleton getInstance() { return SingletonHolder.SINGLETON; } private static class SingletonHolder{ private static Singleton SINGLETON=new Singleton(); } } ~~~ 这样就可以保证只有在调用getInstance的时候再去加载内部类,加载内部类的时候再去实例化变量,而这样也很轻松的解决了多线程的问题。 最后一种很不常用但是去推荐使用的形式是枚举的形式,写法如下: ~~~ public enum Singleton { INSTANCE; private Singleton() { } public String[] getName() { final String[] names = new String[3]; names[0] = "青椒"; names[1] = "大白菜"; names[2] = "牛蛙"; return names; } } ~~~ 然后在MainActivity里使用的时候我们可以这样用: ~~~ Log.e("测试你喜欢吃的食物:",Singleton.INSTANCE.getName()[0]); ~~~ **总结:** 使用单例模式还是存在一定的问题的,比如我们虽然使用private保证了类不能被new出来,可是在java的反射机制中,private是没有作用的,这样就不能保证单例实例的唯一性,再比如在饿汉式中,虽然变量是使用类加载器来实例化的能保证为唯一性,可是如果存在多个类加载器就无法保证唯一性,所以今后能不使用单例还是避免使用它吧,如果真的没办法必须使用,建议使用静态内部类、双重锁、枚举这3种的一种,优先考虑使用枚举。ok,今天的单例就说到这里了,我们慢慢的把26中设计模式都给大家过一遍,期待我的更新吧!!