宁波Java培训
达内宁波中心

13429669395

热门课程

java实现单例的几种方式

  • 时间:2015-11-16
  • 发布:宁波达内
  • 来源:达内培训


    有简单、高效的方法可以实现单例模式,但是单例的完整性却无法难以确保。

    单例模式是指某个类只被实例化一次,用来表示全局或系统范围的组件。单例模式常用于日志记录、工厂、窗口管理器和平台组件管理等。达内java培训专家找出了如下几种java实现单例的方法,比较各自的利弊。

    1、Final字段

    这种方法将构造函数私有化,向外提供一个公有的static final对象:

public class FooSingleton {  
    public final static FooSingleton INSTANCE = new FooSingleton();  
    private FooSingleton() { }  
    public void bar() { }  
}  

    类加载时,static对象被初始化,此时私有的构造函数被第一次也是最后一次调用。即使在类初始化前有多个线程调用此类,JVM也能保证线程继续运行时该类已完整初始化。然而,使用反射和setAccessible(true)方法,可以创建其他新的实例:

Constructor[] constructors = FooSingleton.class.getDeclaredConstructors();
Constructor constructor = constructors[0];
constructor.setAccessible(true);
FooSingleton spuriousFoo = (FooSingleton) constructor.newInstance(new Object[0]);

    我们需要修改构造函数,使其免于多次调用,修改FooSingleton构造函数,可以防范此类攻击:

public class FooSingleton2 {
    private static boolean INSTANCE_CREATED;
    public final static FooSingleton2 INSTANCE = new FooSingleton2();
    private FooSingleton2() {
        if (INSTANCE_CREATED) {
            throw new IllegalStateException("You must only create one instance of this class");
        } else {
            INSTANCE_CREATED = true;
        }
    }
    public void bar() { }
}

    看起来安全一些了,但其实依然可以轻松创建新的实例。只需修改INSTANCE_CREATED字段,就可以了:  

Field f = FooSingleton2.class.getDeclaredField("INSTANCE_CREATED");
f.setAccessible(true);
f.set(null, false);
Constructor[] constructors = FooSingleton2.class.getDeclaredConstructors();
Constructor constructor = constructors[0];
constructor.setAccessible(true);
FooSingleton2 spuriousFoo = (FooSingleton2) constructor.newInstance(new Object[0]);

    此方案经测试并不可行。

    2、静态工厂

    该方法下,公有的成员类似静态工厂:

public class FooSingleton3 {
    public final static FooSingleton3 INSTANCE = new FooSingleton3();
    private FooSingleton3() { }
    public static FooSingleton3 getInstance() { return INSTANCE; }
    public void bar() { }
}

    getInstance()方法返回的永远是同一个对象引用。这个方案也无法防范反射,但依然有其优点。不改变API的情况下,可以实现改变单例。getInstance()出现在几乎所有的单例实现中,标志着确实是一个单例模式。

    3、延迟加载的单例模式

    希望尽可能延迟单例的创建,可以使用延迟初始化方法,当getInstance()方法第一次调用时线程安全地创建单例。

public class FooSingleton4 {  
    private FooSingleton4() {  
    }  
    public static FooSingleton4 getInstance() {  
        return FooSingleton4Holder.INSTANCE;  
    }  
    private static class FooSingleton4Holder {  
        private static final FooSingleton4 INSTANCE = new FooSingleton4();  
    }  
}  

    4、注意序列化问题

   为防止单例实现序列化,需要将所有字段声明为transient并提供一个自定义的readResolve()方法返回唯一实例INSTANCE的引用。

    5、枚举

    用枚举作为单例INSTANCE的容器:

public enum FooEnumSingleton {
    INSTANCE;
    public static FooEnumSingleton getInstance() { return INSTANCE; }
    public void bar() { }
}

    我们似乎轻松的防范了序列化、克隆和反射的攻击。如下代码所示,绕过这些保护还是很容易的:

Constructor con = FooEnumSingleton.class.getDeclaredConstructors()[0];
 Method[] methods = con.getClass().getDeclaredMethods();
 for (Method method : methods) {
     if (method.getName().equals("acquireConstructorAccessor")) {
         method.setAccessible(true);
         method.invoke(con, new Object[0]);
     }
  }
  Field[] fields = con.getClass().getDeclaredFields();
  Object ca = null;
  for (Field field : fields) {
      if (field.getName().equals("constructorAccessor")) {
          field.setAccessible(true);
          ca = field.get(con);
      }
  }
  Method method = ca.getClass().getMethod("newInstance", new Class[]{Object[].class});
  method.setAccessible(true);
  FooEnumSingleton spuriousEnum = (FooEnumSingleton) method.invoke(ca, new Object[]{new Object[]{"SPURIOUS_INSTANCE", 1}});
  printInfo(FooEnumSingleton.INSTANCE);
  printInfo(spuriousEnum);
}
private static void printInfo(FooEnumSingleton e) {
    System.out.println(e.getClass() + ":" + e.name() + ":" + e.ordinal());
}

    执行这段代码,得到结果:

class com.blogspot.minborgsjavapot.singleton.FooEnumSingleton:INSTANCE:0
class com.blogspot.minborgsjavapot.singleton.FooEnumSingleton:SPURIOUS_INSTANCE:1

    枚举的缺点是它无法从另一个基类继承,因为它已经继承自java.lang.Enum。枚举的优点是,假如希望有更多例,只需要增加新的枚举实例即可。

    达内java培训专家总结,枚举是实现单例模式的简单、高效的方法,想要有继承或懒汉式加载,也可以选择延迟初始化方案。





上一篇:选择java 8 的原因何在
下一篇:达内:java把dbf文件写入远程服务器

达内java大数据班就业喜报,最高月薪达18000元

795万高校毕业生创历史新高,2017届毕业生就业近况几何?

达内Linux学员毕业2周就业率96%,最高薪资10000元

达内教育总裁韩少云受邀出席GIE国际教育峰会做主题演讲

选择城市和中心
贵州省

广西省

海南省