我正在尝试找出我们系统中的问题,但以下代码让我担心。主 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()未同步,那么您也可能遇到线程问题。

其他提示

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

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

(来自 GeekAndPoke ):

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

我要看一下的是 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 ”一书中提到了约书亚布洛赫。

  1. Eager Initialziation,如上[EJ Item 3]
  2. Lazy Initalization Holder Class Idiom [EJ Item 71]
  3. 枚举类型[EJ第3项]
  4. 使用'volatile'静态字段双重检查锁定[EJ Item 71]
  5. 解决方案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;
}

}

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top