单例模式
单例模式这里就不用情景模式讲解了,在平时开发中此模式还是用的比较多的。在WinForm
开发的时候,一般设置程序都是使用的单例开发。
定义:
单例模式属于创建型模式,它用于保证一个类仅有一个实例,并提供一个访问它的全局访问点。
要点:
- 某个类只有有一个实例
- 它必须自己创建这个实例
- 他必须自行向整个系统提供这个实例
和工具类的区别:
在刚开始接触单例的时候,一直不明白为什么不直接都用static
属性,这样明明也是单独的一份。
其实单例相对于工具类(utils
)最大的区别在于单例是一个对象,而工具类都是静态方法。
单例相对于工具类的优势在于:
- 更加便于编写第一次初始化的代码,虽然工具类使用
static
块也可以,但是没有单例容易管理 - 单例可以使用懒汉式延迟初始化,在一些初始化代价比较高的地方比较实用,而工具类不行
- 单例作为一个对象,可以很方便的继承和实现接口,而工具类不行
就比如在winform
的设置程序中,我们写的设置程序都是继承于Form
窗体的,而此时就只能使用单例,而且Form
窗体更像是一个对象,因此使用单例也更加合适。
Java 单例的几种写法
- 饿汉式
public class EagerlyInitializedSingleton { private static final EagerlyInitializedSingleton instance=new EagerlyInitializedSingleton(); public EagerlyInitializedSingleton getInstance(){ return instance; } private EagerlyInitializedSingleton(){}; }
- 懒汉式
//1.双重锁校验,优化性能,防止多次锁 //2.volatile 关键字防止JVM优化instance=new LazilyBasicInitializedSingleton()的顺序 //3.在其他方式中,还有一种基础版的懒汉式,也就是不加锁的方法,不建议在任何情况下使用,因为并不能 // 非常确定后面的需求中,这个类可能就被在多线程中使用了,况且加上了双重锁以后,性能和不加锁没什么区 // 别 public class LazilyBasicInitializedSingleton { private static volatile LazilyBasicInitializedSingleton instance; private LazilyBasicInitializedSingleton() { } public static LazilyBasicInitializedSingleton getInstance() { if (instance == null) { synchronized (LazilyBasicInitializedSingleton.class) { if (instance == null) { instance = new LazilyBasicInitializedSingleton(); } } } return instance; } }
- 枚举
public enum EnumInitializedSingleton { INSTANCE; }
- 内部类
public class InternalClassSingleton { public static InternalClassSingleton getInstance() { return InnerClass.instance; } private static class InnerClass { private static InternalClassSingleton instance = new InternalClassSingleton(); } private InternalClassSingleton(){ } }
下面详细解释下上面的方式:
- 饿汉式:饿汉式是指直接在声明类的时候就初始化,这种是比较方便的内部类方式,缺点就在于只要这个类被加载了(比如引用这个类的某个静态属性等),构造方法就会被调用,如果构造方法比较大,可能会引起不必要的性能消耗,
第二点就在于,上面的单例是可以被攻击的,可以通过反射直接
newInstance
一个新的对象,也可以通过序列化和反序列化生成新的对象,防守办法虽然有,但是比较麻烦 -
懒汉式:懒汉式是指真正需要使用这个对象的时候,才初始化这个对象,没有了饿汉式的缺点,但是编写比较复杂,和简单的饿汉式一样,也容易被攻击
-
枚举:枚举是JDK1.5中自带的一种语法,这种语法是天生完美支持内部类的,因为枚举就是一种特殊的类,但是值得注意的是枚举和饿汉式的形式一样,只要属性被引用,就会初始化对象,
枚举是默认有防守反射和序列化和反序列化的,因此使用枚举实现单例类,是一个非常不错的选择
-
内部类:说实话,第一次看见这种方式实现内部类的时候,比较震惊。能想出这种方式的人,一定是能灵活把握JAVA语法的人。这里内部类利用的主要的一点就是内部类能够访问外部类的私有方法,因此在加载外部类的时候,由于没有使用内部类的任何属性,内部类不会被加载,而在需要使用内部类的时候,才会加载内部类的属性,从而实现了懒汉式的单例类,但是内部类的方式,同样存在序列化和反射攻击,因此可以根据情况进行选择。