这个Java单例是否可以在WebSphere 6中重复重建?

我正在尝试追踪我们系统中的问题,以下代码让我担心。 主servlet中的doPost()方法发生以下情况(名称已被更改以保护有罪):

... if(Single.getInstance().firstTime()){ doPreperations(); } normalResponse(); ... 

单身’单身’看起来像这样:

 private static Single theInstance = new Single(); private Single() { ...load properties... } public static Single getInstance() { return theInstance; } 

通过设置为使用静态初始化程序而不是在getInstance()方法中检查null theInstance的方式,这可以一次又一次地重建吗?

PS – 我们正在使用Java 1.4上的App运行WebSphere 6

我在Sun的网站上发现了这个:

不同类加载器同时加载多个单身人士

当两个类加载器加载一个类时,实际上有两个类的副本,每个类都可以拥有自己的Singleton实例。 这在某些servlet引擎(例如iPlanet)中运行的servlet中尤为重要,其中每个servlet默认使用自己的类加载器。 实际上,访问联合Singleton的两个不同的servlet将获得两个不同的对象。

多个类加载器比您想象的更常见。 当浏览器从网络加载类以供applet使用时,它们为每个服务器地址使用单独的类加载器。 类似地,Jini和RMI系统可以为它们下载类文件的不同代码库使用单独的类加载器。 如果您自己的系统使用自定义类加载器,则可能会出现所有相同的问题。

如果由不同的类加载器加载,则具有相同名称的两个类(甚至相同的包名称)将被视为不同 – 即使实际上它们是逐字节的同一个类。 不同的类加载器表示区分类的不同名称空间(即使类的名称相同),因此两个MySingleton类实际上是不同的。 (请参阅参考资料中的“类加载器作为命名空间机制”。)由于两个Singleton对象属于同一名称的两个类,因此乍一看它会出现两个同一类的Singleton对象。

引用 。

除了上述问题,如果firstTime()未同步,那么您也可能遇到线程问题。

不,它不会一次又一次地建造。 它是静态的,因此只有在类加载器第一次触摸类时才会构造一次。

唯一的例外 – 如果您碰巧有多个类加载器。

(来自GeekAndPoke ):

替代文字

正如其他人所提到的,静态初始化程序只能为每个类加载器运行一次。

我要看一下的是firstTime()方法 – 为什么doPreparations()的工作不能在单例本身内处理?

听起来像一堆令人讨厌的依赖。

使用静态初始化程序和延迟初始化之间完全没有区别。 事实上,搞乱延迟初始化要容易得多,这也会强制实现同步。 JVM保证静态初始化程序始终在访问类之前运行,并且只会发生一次。

那就是说JVM不保证你的类只会加载一次。 但是,即使它被加载多次,您的Web应用程序仍将只看到相关的单例,因为它将加载到Web应用程序类加载器或其父级中。 如果部署了多个Web应用程序,则将为每个应用程序调用firstTime()一次。

要检查的最明显的事情是firstTime()需要同步,并且在退出该方法之前设置firstTime标志。

不,它不会创建“单个”的多个副本。 (稍后将访问类加载器问题)

您概述的实现在Briant Goetz的书“ Java Concurrency in Practice ”中被描述为“Eager Initialization”。

 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 1.5和1.6中Java内存模型的许多弱点。

使用Java 1.5和1.6,您有几种选择,Joshua Bloch在“ Effective Java ”一书中提到了它们。

  1. 渴望初始化,如上所述[EJ第3项]
  2. Lazy Initalization Holder Class Idiom [EJ Item 71]
  3. 枚举类型[EJ第3项]
  4. 使用’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没有。 也许詹姆斯可以为他的“类加载器”和“最终”要求添加更多颜色以保证正确性,

话虽如此,使用’静态最终’会产生副作用。 当Java编译器看到’static final’并试图尽可能地内联它时,它非常具有攻击性。 这是在Jeremy Manson的博客文章中提到的。

这是一个简单的例子。

档案:A.java

 public class A { final static String word = "Hello World"; } 

档案: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;

公共类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; } 

}