Singleton指仅仅被实例一次的类,它可以控制实例个数节省系统资源,当要求系统中类对象只允许一个实例对象,那么Singleton是一个好的选择.
《Effective Java》提供了3种Singleton的实现方式
一:私有构造器
公有的静态成员是个final域. 私有构造器仅被调用一次,用来实例化公有静态成员.
由于只有私有构造器而没有其他构造器,所以保证了全局唯一性:一旦类别实例化,只会存在一个实例
在这种情况下可以通过反射调用私有构造器,所以可以修改构造器,当创建第二个实例的时候抛出异常.
二:公有的成员是个静态工厂方法
懒汉式与饿汉式都是通过这种方式达到单例的效果.
//饿汉式
public class Singleton{
private Singleton(){
}
private static final Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
//懒汉式
public class Singleton {
private static Singleton instance = null;
private static Object lockObject = new Object();
//私有构造方法
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null){
synchronized (lockObject){
if (instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
三:编写一个包含单元素的枚举类型
这种放法更加的简洁,无偿的提供序列化禁止,绝对防止多次实例化,在面对复杂的序列化或者反射攻击时依然能够达到效果.
为什么枚举可以实现单例:
枚举来说,其实Enum就是一个普通的类,它继承自java.lang.Enum类。
public enum DataSourceEnum {
DATASOURCE;
}
把上面枚举编译后的字节码反编译,得到的代码如下:
public final class DataSourceEnum extends Enum<DataSourceEnum> {
public static final DataSourceEnum DATASOURCE;
public static DataSourceEnum[] values();
public static DataSourceEnum valueOf(String s);
static {};
}
由反编译后的代码可知,DATASOURCE 被声明为 static 的,可以知道虚拟机会保证一个类的<clinit>() 方法在多线程环境中被正确的加锁、同步。所以,枚举实现是在实例化时是线程安全。
接下来看看序列化问题:
Java规范中规定,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,因此在枚举类型的序列化和反序列化上,Java做了特殊的规定。
在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过 java.lang.Enum 的 valueOf() 方法来根据名字查找枚举对象。
也就是说,以下面枚举为例,序列化的时候只将 DATASOURCE 这个名称输出,反序列化的时候再通过这个名称,查找对于的枚举类型,因此反序列化后的实例也会和之前被序列化的对象实例相同。
public enum DataSourceEnum {
DATASOURCE;
}
由此可知,枚举天生保证序列化单例
Spring中的Singleton
Spring对bean实例的创建是采用单例注册表的方式进行实现的,而这个注册表的缓存是HashMap对象,如果配置文件中的配置信息不要求使用单例,Spring会采用新建实例的方式返回对象实例.
具体实现方法尚未深究,留待以后仔细研究.