这个 Java 单例可以在 WebSphere 6 中重复重建吗?
题
我正在尝试找出我们系统中的问题,但以下代码让我担心。主 servlet 中的 doPost() 方法中发生以下情况(名称已更改以保护有罪者):
...
if(Single.getInstance().firstTime()){
doPreperations();
}
normalResponse();
...
单例“Single”看起来像这样:
private static Single theInstance = new Single();
private Single() {
...load properties...
}
public static Single getInstance() {
return theInstance;
}
通过将其设置为使用静态初始值设定项而不是在 getInstance() 方法中检查 null theInstance 的方式,是否可以一遍又一遍地重建它?
PS - 我们正在运行 WebSphere 6 以及 Java 1.4 上的应用程序
解决方案
我在Sun的网站上发现了这个:
由不同类加载器同时加载的多个单例
当两个类加载器加载一个类时, 你实际上有两份副本 上课,每个人都可以拥有自己的 单例实例。那是 与servlet特别相关 在某些servlet引擎中运行 (例如iPlanet),每个 servlet默认使用自己的类 装载机。两个不同的servlet 访问联合单身人士将进入 事实上,得到两个不同的对象。
多个类加载器更多 通常比你想象的要好。什么时候 浏览器从网络加载类 供applet使用,他们使用 为每个服务器分离类加载器 地址。同样,Jini和RMI 系统可以使用单独的类 不同代码库的加载器 从中下载类文件。 如果您自己的系统使用自定义类 装载机,所有相同的问题可能 出现。
如果由不同的类加载器加载, 两个同名的班级,甚至 相同的包名称,被视为 不同 - 即使事实上它们是 byte-for-byte是同一个类。该 不同的类装载机代表 区分的不同名称空间 课程(即使班级' 名字是一样的),这两个
MySingleton
类实际上就是这样 不同。 (参见“类装载机” 名称空间机制“在参考资料中。) 由于两个Singleton对象属于 两个同名的类,它会 乍一看似乎有 两个Singleton对象相同 类。
Citation 。
除了上述问题,如果 firstTime()
未同步,那么您也可能遇到线程问题。
其他提示
正如其他人所提到的,静态初始值设定项只能为每个类加载器运行一次。
我要看一下的是 firstTime()
方法 - 为什么 doPreparations()
中的工作不能在单例本身内处理?
听起来像是一组讨厌的依赖。
使用静态初始化程序和延迟初始化之间完全没有区别。事实上,搞乱延迟初始化要容易得多,这也会强制实现同步。 JVM保证静态初始化程序始终在访问类之前运行,并且只发生一次。
那就是说JVM不保证你的类只会加载一次。但是,即使它被加载多次,您的Web应用程序仍将只看到相关的单例,因为它将加载到Web应用程序类加载器或其父级中。如果部署了多个Web应用程序,则将为每个应用程序调用firstTime()一次。
要检查的最明显的事情是需要同步firstTime()并在退出该方法之前设置firstTime标志。
不,它不会创建'Single'的多个副本。 (稍后将访问类加载器问题)
您概述的实施在Briant Goetz的书中被描述为'Eager Initialization' - ' Java Concurrency in Practice ”。
public class Single
{
private static Single theInstance = new Single();
private Single()
{
// load properties
}
public static Single getInstance()
{
return theInstance;
}
}
但是,代码不是你想要的。您的代码在创建实例后尝试执行延迟初始化。这要求所有客户端库在使用之前执行'firstTime()/ doPreparation()'。你将依赖客户做正确的事情,使代码非常脆弱。
您可以按以下方式修改代码,这样就不会有任何重复的代码。
public class Single
{
private static Single theInstance = new Single();
private Single()
{
// load properties
}
public static Single getInstance()
{
// check for initialization of theInstance
if ( theInstance.firstTime() )
theInstance.doPreparation();
return theInstance;
}
}
不幸的是,这是延迟初始化的糟糕实现,这在并发环境(如J2EE容器)中不起作用。
有很多关于Singleton初始化的文章,特别是关于内存模型的文章。 JSR 133 解决了Java内存中的许多弱点Java 1.5和1.6中的模型。
使用Java 1.5& 1.6,你有几个选择,他们在“ Effective Java ”一书中提到了约书亚布洛赫。
- Eager Initialziation,如上[EJ Item 3]
- Lazy Initalization Holder Class Idiom [EJ Item 71]
- 枚举类型[EJ第3项]
- 使用'volatile'静态字段双重检查锁定[EJ Item 71] 醇>
解决方案3和4仅适用于Java 1.5及更高版本。所以最好的解决方案是#2。
这是伪实现。
public class Single
{
private static class SingleHolder
{
public static Single theInstance = new Single();
}
private Single()
{
// load properties
doPreparation();
}
public static Single getInstance()
{
return SingleHolder.theInstance;
}
}
请注意,'doPreparation()'在构造函数内部,因此您可以保证获得正确初始化的实例。此外,您正在回顾JVM的延迟类加载,并且不需要任何同步'getInstance()'。
有一件事您注意到静态字段theInstance 不是'final'。 Java Concurrency上的示例没有'final'但EJ有。也许James可以为他的“classloader”和“final”的答案添加更多颜色,以保证正确性,
话虽如此,使用'静态最终'会产生副作用。当Java编译器看到'static final'并试图尽可能地内联它时,它是非常积极的。这可以在 Jeremy Manson的博客上发表
这是一个简单的例子。
文件:A.java
public class A
{
final static String word = "Hello World";
}
file:B.java
public class B
{
public static void main(String[] args) {
System.out.println(A.word);
}
}
在编译A.java和B.java之后,将A.java更改为以下。
文件:A.java
public class A
{
final static String word = "Goodbye World";
}
您重新编译'A.java'并重新运行B.class。你得到的输出是
Hello World
至于类加载器问题,答案是肯定的,你可以在多个类加载器中拥有多个Singleton实例。您可以在维基百科上找到更多信息。还有一篇关于 Websphere 的特定文章。
我唯一想改变的是Singleton实现(除了根本不使用Singleton)是使实例字段最终。静态字段将在类加载时初始化一次。由于类是懒惰加载的,因此您可以免费实现惰性实例化。
当然,如果它是从单独的类加载器加载的,你会得到多个“单例”,但这是Java中每个单例习惯用语的限制。
编辑:虽然firstTime()和doPreparations()位看起来很可疑。它们不能被移动到单例实例的构造函数中吗?
否 - 的静态初始化 instance
只会进行一次。需要考虑两件事:
- 这不是线程安全的(实例没有“发布”到主内存)
- 你的
firstTime
方法可能会被多次调用,除非正确同步
理论上它只会被构建一次。但是,这种模式在各种应用程序服务器中中断,您可以在其中获取“单例”类的多个实例(因为它们不是线程安全的)。
此外,单身人士模式受到了很多批评。例如,参见辛格尔顿我爱你,但你让我失望了
只有在类加载器加载类时才会加载一次。 这个例子提供了一个更好的Singleton实现,但它尽可能地延迟加载并且是线程安全的。 此外,它适用于所有已知版本的Java。 该解决方案在不同的Java编译器和虚拟机中最具可移植性。
public class Single {
private static class SingleHolder {
private static final Single INSTANCE = new Single();
}
private Single() {
...load properties...
}
public static Single getInstance() {
return SingleHolder.INSTANCE;
}
}
与调用getInstance()的时刻相比,内部类不会被引用(因此不会由类加载器加载)。因此,该解决方案是线程安全的,不需要特殊的语言结构(即易失性和/或同步)。
单个实例不是最终的必要条件(这根本不是一个好主意,因为这样可以避免使用其他模式切换它的行为)。
在下面的代码中,您可以看到它如何仅实例化一次(第一次调用构造函数)
包裹日期;
import java.util.Date;
public class USDateFactory实现DateFactory { private static USDateFactory usdatefactory = null;
private USDateFactory () { }
public static USDateFactory getUsdatefactory() {
if(usdatefactory==null) {
usdatefactory = new USDateFactory();
}
return usdatefactory;
}
public String getTextDate (Date date) {
return null;
}
public NumericalDate getNumericalDate (Date date) {
return null;
}
}