什么是单例?为什么要用单例?
一个类被设计出来,就代表它表示具有某种行为(方法),属性(成员变量),而一般情况下,当我们想使用这个类时,会使用new关键字,这时候jvm会帮我们构造一个该类的实例。而我们知道,对于new这个关键字以及该实例,相对而言是比较耗费资源的。所以如果我们能够想办法在jvm启动时就new好,或者在某一次实例new好以后,以后不再需要这样的动作,就能够节省很多资源了。
哪些类可以使用单例?
一般而言,我们总是希望无状态的类能够设计成单例,那这个无状态代表什么呢? 简单而言,对于同一个实例,如果多个线程同时使用,并且不使用额外的线程同步手段,不会出现线程同步的问题,我们就可以认为是无状态的,再简单点:一个类没有成员变量,或者它的成员变量也是无状态的,我们就可以考虑设计成单例。
实现方法
好了,我们已经知道什么是单例,为什么要使用单例了,那我们接下来继续讨论下怎么实现单例。
一般来说,我们可以把单例分为行为上的单例和管理上的单例。行为上的单例代表不管如何操作(此处不谈cloneable,反射),至始至终jvm中都只有一个类的实例,而管理上的单例则可以理解为:不管谁去使用这个类,都要守一定的规矩,比方说,我们使用某个类,只能从指定的地方’去拿‘,这样拿到就是同一个类了。
而对于管理上的单例,相信大家最为熟悉的就是spring了,spring将所有的类放到一个容器中,以后使用该类都从该容器去取,这样就保证了单例。
所以这里我们剩下的就是接着来谈谈如何实现行为上的单例了。一般来说,这种单例实现有两种思路,私有构造器,枚举。
枚举实现单例
枚举实现单例是最为推荐的一种方法,因为就算通过序列化,反射等也没办法破坏单例性,例子:
public enum SingletonEnum {
INSTANCE;
public static void main(String[] args) {
System.out.println(SingletonEnum.INSTANCE == SingletonEnum.INSTANCE);
}
}
结果自然是true,而如果我们尝试使用反射破坏单例性:
public enum BadSingletonEnum {
/**
*
*/
INSTANCE;
public static void main(String[] args) throws Exception{
System.out.println(BadSingletonEnum.INSTANCE == BadSingletonEnum.INSTANCE);
Constructor<BadSingletonEnum> badSingletonEnumConstructor = BadSingletonEnum.class.getDeclaredConstructor();
badSingletonEnumConstructor.setAccessible(true);
BadSingletonEnum badSingletonEnum = badSingletonEnumConstructor.newInstance();
System.out.println(BadSingletonEnum.INSTANCE == badSingletonEnum);
}
}
结果如下:
Exception in thread "main" java.lang.NoSuchMethodException: cn.jsbintask.BadSingletonEnum.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at cn.jsbintask.BadSingletonEnum