gpt4 book ai didi

java - 现在更好的 Java 单例模式?

转载 作者:搜寻专家 更新时间:2023-10-30 21:01:44 26 4
gpt4 key购买 nike

这个问题在这里已经有了答案:





What is an efficient way to implement a singleton pattern in Java? [closed]

(29 个回答)


5年前关闭。




您知道,自从 Java 5 发布以来,在 Java 中编写单例模式的推荐方法是使用枚举。

public enum Singleton {
INSTANCE;
}

但是,我不喜欢这样做 - 强制客户端使用 Singleton.INSTANCE 以便访问单例实例。
也许,将单例隐藏在普通类中的更好方法,并提供对单例设施的更好访问:
public class ApplicationSingleton {
private static enum Singleton {
INSTANCE;

private ResourceBundle bundle;

private Singleton() {
System.out.println("Singleton instance is created: " +
System.currentTimeMillis());

bundle = ResourceBundle.getBundle("application");
}

private ResourceBundle getResourceBundle() {
return bundle;
}

private String getResourceAsString(String name) {
return bundle.getString(name);
}
};

private ApplicationSingleton() {}

public static ResourceBundle getResourceBundle() {
return Singleton.INSTANCE.getResourceBundle();
}

public static String getResourceAsString(String name) {
return Singleton.INSTANCE.getResourceAsString(name);
}
}

因此,客户端现在可以简单地编写:
ApplicationSingleton.getResourceAsString("application.name")

例如。
哪个好得多:
Singleton.INSTANCE.getResourceAsString("application.name")

所以,问题是:这是正确的做法吗?这段代码是否有任何问题(线程安全?)?它是否具有“枚举单例”模式的所有优点?似乎它需要从两个世界中取得更好的成绩。你怎么认为?有没有更好的方法来实现这一目标?
谢谢。

编辑
@全部
在 Effective Java,第 2 版中首先提到了单例模式的枚举用法: wikipedia:Java Enum Singleton .我完全同意我们应该尽可能减少 Singleton 的使用,但我们不能完全摆脱它们。
在我提供另一个示例之前,让我说,带有 ResourceBundle 的第一个示例只是一个案例,示例本身(和类名称)并非来自实际应用程序。但是,需要说的是,我不知道 ResourceBundle 缓存管理,感谢您提供的信息)

下面,单例模式有两种不同的方法,第一种是使用 Enum 的新方法,第二种是我们大多数人以前使用的标准方法。我试图展示它们之间的显着差异。

使用枚举的单例:
ApplicationSingleton 类是:
public class ApplicationSingleton implements Serializable {
private static enum Singleton {
INSTANCE;

private Registry registry;

private Singleton() {
long currentTime = System.currentTimeMillis();
System.out.println("Singleton instance is created: " +
currentTime);

registry = new Registry(currentTime);
}

private Registry getRegistry() {
return registry;
}

private long getInitializedTime() {
return registry.getInitializedTime();
}

private List<Registry.Data> getData() {
return registry.getData();
}
};

private ApplicationSingleton() {}

public static Registry getRegistry() {
return Singleton.INSTANCE.getRegistry();
}

public static long getInitializedTime() {
return Singleton.INSTANCE.getInitializedTime();
}

public static List<Registry.Data> getData() {
return Singleton.INSTANCE.getData();
}
}

注册表类是:
public class Registry {
private List<Data> data = new ArrayList<Data>();
private long initializedTime;

public Registry(long initializedTime) {
this.initializedTime = initializedTime;
data.add(new Data("hello"));
data.add(new Data("world"));
}

public long getInitializedTime() {
return initializedTime;
}

public List<Data> getData() {
return data;
}

public class Data {
private String name;

public Data(String name) {
this.name = name;
}

public String getName() {
return name;
}
}
}

和测试类:
public class ApplicationSingletonTest {     

public static void main(String[] args) throws Exception {

String rAddress1 =
ApplicationSingleton.getRegistry().toString();

Constructor<ApplicationSingleton> c =
ApplicationSingleton.class.getDeclaredConstructor();
c.setAccessible(true);
ApplicationSingleton applSingleton1 = c.newInstance();
String rAddress2 = applSingleton1.getRegistry().toString();

ApplicationSingleton applSingleton2 = c.newInstance();
String rAddress3 = applSingleton2.getRegistry().toString();


// serialization

ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(applSingleton1);

ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(byteOut.toByteArray()));
ApplicationSingleton applSingleton3 = (ApplicationSingleton) in.readObject();

String rAddress4 = applSingleton3.getRegistry().toString();

List<Registry.Data> data = ApplicationSingleton.getData();
List<Registry.Data> data1 = applSingleton1.getData();
List<Registry.Data> data2 = applSingleton2.getData();
List<Registry.Data> data3 = applSingleton3.getData();

System.out.printf("applSingleton1=%s, applSingleton2=%s, applSingleton3=%s\n", applSingleton1, applSingleton2, applSingleton3);
System.out.printf("rAddr1=%s, rAddr2=%s, rAddr3=%s, rAddr4=%s\n", rAddress1, rAddress2, rAddress3, rAddress4);
System.out.printf("dAddr1=%s, dAddr2=%s, dAddr3=%s, dAddr4=%s\n", data, data1, data2, data3);
System.out.printf("time0=%d, time1=%d, time2=%d, time3=%d\n",
ApplicationSingleton.getInitializedTime(),
applSingleton1.getInitializedTime(),
applSingleton2.getInitializedTime(),
applSingleton3.getInitializedTime());
}

}

这是输出:
Singleton instance is created: 1304067070250
applSingleton1=ApplicationSingleton@18a7efd, applSingleton2=ApplicationSingleton@e3b895, applSingleton3=ApplicationSingleton@6b7920
rAddr1=Registry@1e5e2c3, rAddr2=Registry@1e5e2c3, rAddr3=Registry@1e5e2c3, rAddr4=Registry@1e5e2c3
dAddr1=[Registry$Data@1dd46f7, Registry$Data@5e3974], dAddr2=[Registry$Data@1dd46f7, Registry$Data@5e3974], dAddr3=[Registry$Data@1dd46f7, Registry$Data@5e3974], dAddr4=[Registry$Data@1dd46f7, Registry$Data@5e3974]
time0=1304067070250, time1=1304067070250, time2=1304067070250, time3=1304067070250

应该提到的是:
  • 单例实例仅创建一次
  • 是的,ApplicationSingletion 有几个不同的实例,但它们都包含相同的 Singleton 实例
  • 所有 的注册表内部数据都相同不同 ApplicationSingleton 实例

  • 所以,总结一下:枚举方法工作正常,防止通过反射攻击重复创建单例,并在序列化后返回相同的实例。

    单例使用标准方法:

    ApplicationSingleton 类是:
    public class ApplicationSingleton implements Serializable {
    private static ApplicationSingleton INSTANCE;

    private Registry registry;

    private ApplicationSingleton() {
    try {
    Thread.sleep(10);
    } catch (InterruptedException ex) {}
    long currentTime = System.currentTimeMillis();
    System.out.println("Singleton instance is created: " +
    currentTime);
    registry = new Registry(currentTime);
    }

    public static ApplicationSingleton getInstance() {
    if (INSTANCE == null) {
    return newInstance();
    }
    return INSTANCE;

    }

    private synchronized static ApplicationSingleton newInstance() {
    if (INSTANCE != null) {
    return INSTANCE;
    }
    ApplicationSingleton instance = new ApplicationSingleton();
    INSTANCE = instance;

    return INSTANCE;
    }

    public Registry getRegistry() {
    return registry;
    }

    public long getInitializedTime() {
    return registry.getInitializedTime();
    }

    public List<Registry.Data> getData() {
    return registry.getData();
    }
    }

    Registry 类是(注意 Registry 和 Data 类应该明确地实现 Serializable 以便序列化工作):
    //now Registry should be Serializable in order serialization to work!!!
    public class Registry implements Serializable {
    private List<Data> data = new ArrayList<Data>();
    private long initializedTime;

    public Registry(long initializedTime) {
    this.initializedTime = initializedTime;
    data.add(new Data("hello"));
    data.add(new Data("world"));
    }

    public long getInitializedTime() {
    return initializedTime;
    }

    public List<Data> getData() {
    return data;
    }

    // now Data should be Serializable in order serialization to work!!!
    public class Data implements Serializable {
    private String name;

    public Data(String name) {
    this.name = name;
    }

    public String getName() {
    return name;
    }
    }
    }

    ApplicationSingletionTest 类是(大体相同):
    public class ApplicationSingletonTest {     

    public static void main(String[] args) throws Exception {

    String rAddress1 =
    ApplicationSingleton.getInstance().getRegistry().toString();

    Constructor<ApplicationSingleton> c =
    ApplicationSingleton.class.getDeclaredConstructor();
    c.setAccessible(true);
    ApplicationSingleton applSingleton1 = c.newInstance();
    String rAddress2 = applSingleton1.getRegistry().toString();

    ApplicationSingleton applSingleton2 = c.newInstance();
    String rAddress3 = applSingleton2.getRegistry().toString();


    // serialization

    ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
    ObjectOutputStream out = new ObjectOutputStream(byteOut);
    out.writeObject(applSingleton1);

    ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(byteOut.toByteArray()));
    ApplicationSingleton applSingleton3 = (ApplicationSingleton) in.readObject();

    String rAddress4 = applSingleton3.getRegistry().toString();

    List<Registry.Data> data = ApplicationSingleton.getInstance().getData();
    List<Registry.Data> data1 = applSingleton1.getData();
    List<Registry.Data> data2 = applSingleton2.getData();
    List<Registry.Data> data3 = applSingleton3.getData();

    System.out.printf("applSingleton1=%s, applSingleton2=%s, applSingleton3=%s\n", applSingleton1, applSingleton2, applSingleton3);
    System.out.printf("rAddr1=%s, rAddr2=%s, rAddr3=%s, rAddr4=%s\n", rAddress1, rAddress2, rAddress3, rAddress4);
    System.out.printf("dAddr1=%s, dAddr2=%s, dAddr3=%s, dAddr4=%s\n", data, data1, data2, data3);
    System.out.printf("time0=%d, time1=%d, time2=%d, time3=%d\n",
    ApplicationSingleton.getInstance().getInitializedTime(),
    applSingleton1.getInitializedTime(),
    applSingleton2.getInitializedTime(),
    applSingleton3.getInitializedTime());
    }

    }

    这是输出:
    Singleton instance is created: 1304068111203
    Singleton instance is created: 1304068111218
    Singleton instance is created: 1304068111234
    applSingleton1=ApplicationSingleton@16cd7d5, applSingleton2=ApplicationSingleton@15b9e68, applSingleton3=ApplicationSingleton@1fcf0ce
    rAddr1=Registry@f72617, rAddr2=Registry@4f1d0d, rAddr3=Registry@1fc4bec, rAddr4=Registry@1174b07
    dAddr1=[Registry$Data@1256ea2, Registry$Data@82701e], dAddr2=[Registry$Data@1f934ad, Registry$Data@fd54d6], dAddr3=[Registry$Data@18ee9d6, Registry$Data@19a0c7c], dAddr4=[Registry$Data@a9ae05, Registry$Data@1dff3a2]
    time0=1304068111203, time1=1304068111218, time2=1304068111234, time3=1304068111218

    应该提到的是:
  • 单例实例被创建了好几个!次
  • 所有注册表对象都是具有自己数据的不同对象

  • 所以,总结一下:标准方法对于反射攻击很弱,并且在序列化后返回不同的实例,但是对于相同的数据是可以的。

    因此,似乎 Enum 方法更加可靠和健壮。它是当今在 Java 中使用单例模式的推荐方式吗?你怎么认为?
    解释一个有趣的事实:为什么 enum 中的对象可以与其所属的类一起序列化,但没有实现 Serializable?它是功能还是错误?

    最佳答案

    我不知道现在枚举是构建单例的 Java 方式。但是如果你打算这样做,你也可以直接使用枚举。我看不出有什么好的理由将单例封装在一堆静态成员方法后面;一旦你这样做了,你也可以开始编写一个带有私有(private)静态成员的静态类。

    关于java - 现在更好的 Java 单例模式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5822827/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com