【问题标题】:Singleton using "Initialization-on-demand holder idiom"使用“按需初始化持有者习语”的单例
【发布时间】:2014-11-25 13:00:26
【问题描述】:

这个问题与 Initialization On Demand Holder 成语有关。我发现 here 它是线程安全的,没有 final 修饰符。

我希望这不是一个愚蠢的问题。

我有一个使用org.apache.commons.configuration.XMLConfiguration 的配置单例。好吧其实我有三个,以后可以增加。

它有一个默认的文件名来获取配置。

以后我需要能够定义一个新的配置文件名并使用它重新创建实例。

当我使用非线程安全的单例时,我曾经设置新的文件名,然后将包含实例的变量重置为 null,就是这样。下次使用该配置时,会自动使用新的配置文件名进行初始化。

我不能再使用“Initialization-on-demand holder idiom”那样做,因为我不能“消失”持有者类。 (对吧?)

我尝试重新分配包含该实例的静态变量,但显然它不起作用。

这是配置类的共同祖先:

public class ConfiguracionBase {
    protected static String configFileName = "config.xml";

    protected static void abreConfiguracion(XMLConfiguration XMLConfig) throws ConfigurationException {
        //Should be placed in the same directory as this application.
        boolean exists = (new File(configFileName)).exists();
...
        if (exists) {
            try {
                XMLConfig = new XMLConfiguration(configFileName);
            } catch (ConfigurationException e) {
                if (e.getMessage().startsWith("Unable to load the configuration")) {
                    logger.fatal("IMPOSIBLE CONTINUAR: "+e.getMessage()+" ["+configFileName+"]",e);
                    StrUtil.writeToSimpleLog(Level.FATAL , "IMPOSIBLE CONTINUAR: "+e.getMessage()+" ["+configFileName+"]");
                    System.exit(Grales.EXIT_STATUS_ERROR_EN_CONFIGURACION);
                } else {
                    throw e; 
                }
            }

            XMLConfig.setThrowExceptionOnMissing(Boolean.TRUE);

...


    protected String getStringValue( [... some params ...] , XMLConfiguration XMLConfig) {

...

    protected int getIntValue( [... some params ...] , XMLConfiguration XMLConfig) {

...

这是我的配置类之一:

public class Configuracion extends ConfiguracionBase {


    private static class ConfiguracionHolder {
        protected static Configuracion config = new Configuracion();
        protected static XMLConfiguration XMLConfig = null;

        private ConfiguracionHolder() throws ConfigurationException {
            abreConfiguracion(XMLConfig);
        }

    }

    private Configuracion() {
      // Exists only to defeat instantiation.
    }

    public static Configuracion getInstance() throws ConfigurationException {
        System.setProperty("user.timezone", ConfiguracionHolder.config.getTimezoneCFD());
        return ConfiguracionHolder.config;
    }

    public static void setConfigFileName(String configFileName) {
        ConfiguracionBase.configFileName = configFileName;
        ConfiguracionHolder.config = new Configuracion();
    }

...

我的 config.xml(默认配置)的货币格式如下:$,0.00000000;$(,0.00000000)

我的新 configAbsoluteFileName 的货币格式如下:\,0.00000000;-\,0.00000000

当我启动我的应用程序时,我会这样做:

    Configuracion.setConfigFileName(configAbsoluteFileName);

我添加了一个测试方法:

                    logger.debug("************ config file name: " + Configuracion.getConfigFileName() + " *******************");
                    logger.debug("************ money format: " + Configuracion.getInstance().getFormatoDineroHumanos() + " *******************");

即使文件名是新的,格式也是旧的。

我认为这可能是因为配置文件名变量是在 ConfiguracionBase 中定义的,而不是在我的持有者“子”类中。

但我不想在每个配置单例中重复初始化。

希望它很清楚,有人可以阐明它。

谢谢大家。


编辑 1: 替换

    public static void setConfigFileName(String configFileName) {
        ConfiguracionBase.configFileName = configFileName;
        ConfiguracionHolder.config = new Configuracion();
    }

    public static void setConfigFileName(String configFileName) {
        Configuracion.configFileName = configFileName;
        ConfiguracionHolder.config = new Configuracion();
    }i

但没有任何改变


编辑 2:

@fgb 的回答真的很有帮助。非常感谢。

刚刚根据Double-checked_locking维基百科站点添加了一些更改。

我的 ConfiguracionBase.abreConfiguracion 最终返回一个 XMLConfiguration 对象并接收前一个对象,以便在找不到新文件名时保留它:

    protected static XMLConfiguration abreConfiguracion(XMLConfiguration XMLConfig, String configFileName) throws ConfigurationException {
...
        return XMLConfig;
    }

而我的具体配置类有:

// Works with acquire/release semantics for volatile
// Broken under Java 1.4 and earlier semantics for volatile
public class Configuracion extends ConfiguracionBase {

    private static volatile Configuracion config = null;
    private static XMLConfiguration XMLConfig = null;
    private static final Object objLock = new Object();

    private Configuracion() {
          // Exists only to defeat instantiation.
    }

    private Configuracion(String fileName) throws ConfigurationException {
        XMLConfig = abreConfiguracion(XMLConfig, fileName);
    }

    public static Configuracion getInstance() throws ConfigurationException {
        Configuracion result = config;
        if (result == null) {
            synchronized(objLock) {
                result = config;
                if (result == null) {
                    config = result = new Configuracion(configFileName);
                }
            }
        }
        System.setProperty("user.timezone", result.getTimezoneCFD());
        return result;

        /* http://en.wikipedia.org/wiki/Double-checked_locking: 
         * Note the local variable result, 
         * which seems unnecessary. This ensures that in cases where helper is already 
         * initialized (i.e., most of the time), the volatile field is only accessed once 
         * (due to "return result;" instead of "return helper;"), which can improve the 
         * method's overall performance by as much as 25 percent.[5] */
    }
...

现在一切正常。

再次:坦克。


编辑 3:

添加注释并更改 getInstance() 中的返回对象。在此之前我犯了一个错误,所以我没有按照Double-checked_locking wikipedia 网站所说的那样做。

【问题讨论】:

    标签: java multithreading singleton


    【解决方案1】:

    您没有创建ConfiguracionHolder 的实例,因此不会调用其构造函数,也不会调用abreConfiguracion(XMLConfig)

    我不认为你从持有人习语中得到任何好处。只有静态初始化是线程安全的并且只运行一次。如果您想更改 config 实例,那么您需要多次运行配置,而使用该惯用语是不可能的。

    如果您有不同的线程调用getInstance()setConfigFileName(),那么您需要在它们之间进行某种同步。让config volatile 就足够了。

    我会将abreConfiguracion(XMLConfig) 移动到Configuracion 的构造函数中,将config 设为Configuracion 的类变量,并将configFileName 设为Configuracion 构造函数的参数。

    此外,abreConfiguracion 看起来应该返回 XMLConfig 而不是将其作为参数。现在,它似乎没有设置任何在方法之外可见的东西。

    【讨论】:

    • 两个问题:。 1) setConfigFileName 应该只将 config 设置为 null?。像 public static void setConfigFileName(String configFileName) { ConfiguracionBase.configFileName = configFileName; config = null; } 这样的东西或者应该创建一个新的配置。 2)它如何与其他线程同步?
    • 扩展问题编号 1:我应该在 config = null; 之后添加类似这样的内容:Configuracion.getInstance(); 吗?
    • 您需要文件名来创建config,所以我将在setConfigFileName 方法中创建它,让getInstance 只返回configsetConfigFileName 总是会创建一个新实例,因此它不需要双重检查锁定,并且它可能被调用的频率较低,因此不会成为瓶颈。
    猜你喜欢
    • 2016-06-03
    • 2018-09-08
    • 2014-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-01
    相关资源
    最近更新 更多