Singleton 最佳实践-单元素的枚举类型
接下来将分别介绍下实现 Singleton 的几种方法:
第一种:双重校验锁【不推荐使用】
public class Singleton
{
private volatile static Singleton instance;
private Singleton(){};
public static Singleton getInstance()
{
if (instance == null)
{
synchronized (Singleton.class)
{
if (instance == null)
{
instance = new Singleton();
}
}
}
return instance;
}
}
饿汉模式:不会产生安全问题,因为在类加载的时候该对象就已经被创建好了
class DanLi {
private DanLi(){} // 将构造方法私有化 让外部不能创建对象
private static DanLi dl = new DanLi(); // 将对象提前创建好,当类加载的时候就创建了对象
}
增强版:
class DanLi {
private DanLi(){} // 将构造方法私有化 让外部不能创建对象
private static DanLi dl = new DanLi(); // 将对象提前创建好,当类加载的时候就创建了对象
public static DanLi getDanLi(){ // 当调用方法时直接返回对象
return dl;
}
}
变种:在类初始化的时候实例化 INSTANCE
public class Singleton{
private static Singleton instance = null;
private Singleton(){};
static {
instance = new Singleton();
}
public static Singleton getInstance() {
return instance;
}
}
静态内部类
public class Singleton{
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
private Singleton() {};
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
不过,以上几种方法都存在一个共性的问题:它们都可以借助 AccessibleObject.setAccessible 方法通过反射机制,调用私有构造器创建新的实例
一般情况下,我们并不能对类的私有字段进行操作,利用反射也不例外,此时,就可以调用 AccessibleObject.setAccessible(boolean flag) 方法来允许这种访问
if (s1 == s2) {
// 说明创建了一个实例
} else {
// 说明创建了两个不同的实例,如果需要抵御这种攻击,可以在被要求创建第二个实例的时候抛出异常
}
可修改如下:
public class ElvisModified
{
private static boolean flag = false; // 增加标识
private ElvisModified(){
synchronized(ElvisModified.class) {
if(flag == false) // 判断标识
{
flag = !flag;
}
else
{
throw new RuntimeException("单例模式被侵犯!");
}
}
}
private static class SingletonHolder{
private static final ElvisModified INSTANCE = new ElvisModified();
}
public static ElvisModified getInstance() {
return SingletonHolder.INSTANCE;
}
public void doSomethingElse() {
}
}
JDK 1.5 之后,可使用单元素的枚举类型来实现 Singleton
public enum Test {
INSTANCE;
public void dosomething() {
System.out.println(this + " is speaking!");
}
}
// test
public class TestSingleton {
public static void main(String[] args) {
Test t1 = Test.INSTANCE;
t1.dosomething();
Test t2 = Test.INSTANCE;
t2.dosomething();
System.out.println(t1 == t2);
}
}
// result
INSTANCE is speaking!
INSTANCE is speaking!
true
使用单元素的枚举类型实现 Singleton 时,通过反射机制去创建新的实例时会抛出异常。和以前的 s1 == s2 对比,可以看到使用单元素的枚举类型在功能上与公有域方法相近,但是它更加简洁,并且无偿得提供了序列化机制,绝对防止多次实例化。