线程安全的共享对象更新没有性能损失?

时间:2014-08-29 06:29:52

标签: java multithreading semaphore synchronized

我正在努力绕过我的类的线程安全实现,以便在Servlet或代码中的多个“客户”之间共享。

假设我有以下MySingleton类,一个用Configuration对象初始化的单例。但是,可以观察到Configuration对象,因此如果进行了更改,单身人员将订阅通知。重要的关键点:

  • 配置可随时更改(无法预测)

  • 解析配置后,其值将保存到MySingleton的成员字段中

  • 单身人士的公共方法使用这些字段生成返回结果

请参阅以下简化代码:

public class MySingleton 
    implements IConfigurationObserver {
    // Member(s)
    private static volatile MySingleton instance;
    private final Configuration configuration;
    private String firstParam;
    private String secondParam;

    // Constructor(s)
    private MySingleton(Configuration configuration) {
        this.configuration = configuration;
        parseConfiguration();
        configuration.addObserver(this);
    }

    public static MySingleton getInstance(Configuration configuration) {
        // Perform synchronized creation if applicable (double locking technique)
        MySingleton syncInstance = instance;
        if (syncInstance == null) {
            synchronized(MySingleton.class) {
                syncInstance = instance; // Verify once again after synchronizing
                if(syncInstance == null) {  
                    syncInstance = new MySingleton(configuration);
                    instance = syncInstance;
                }
            }
        }

        return syncInstance;
    }

    // Private Method(s)
    private void parseConfiguration() {
        // Import values from the configuration
        this.firstParam = configuration.get(0);
        this.secondParam = configuration.get(1);
    }

    // Public Method(s)
    public String buildSentence() {
        // Build a new string based on values pulled from the configuration
        StringBuilder strBuilder = new StringBuilder();
        strBuilder.append(firstParam);
        strBuilder.append(" - ");
        strBuilder.append(secondParam);

        return strBuilder.toString();
    }

    // Observer Method(s)
    @Override
    public void onConfigurationUpdated() {
        // The configuration has changed. Parse it again.
        parseConfiguration();
    }
}

该类可以自行运行(在单线程环境中),但是我想在多线程场景中消除一些威胁:

  • 如果在很短的时间内对Configuration进行了两次更新,则在第二次呼叫开始之前,对parseConfiguration()的第一次呼叫可能没有完成。这个很容易解决,我可以让parseConfiguration()同步(对吧?)。但是......

  • 当配置通知我们的单身人士时,假设我们正在调用buildSentence()。我不希望buildSentence()为firstParam和secondParam使用旧值的混合(即如果parseConfiguration()已完成中途)。因此,我可以在ConfigurationparseConfiguration()的{​​{1}}对象上放置一个同步块,但后来我遇到了严重的性能损失:我不能有多个并发调用{ {1}}。事实上,对我来说理想的情况是:

    • 如果buildSentence()正在运行且发生buildSentence()更新,则buildSentence()必须等到Configuration结束才能运行

    • 如果parseConfiguration()正在投放,则对buildSentence()的调用必须等到parseConfiguration()结束才能开始

    • 但是,一旦buildSentence()完成,我想允许多个线程同时运行parseConfiguration()。只有在即将发生或正在进行更新时才会发生锁定。

如何重构MySingleton以允许我在上面列出的理想“规则”?有可能吗?


我一直在想一个涉及信号量的解决方案。即:执行parseConfiguration()时,检查信号量是否可用。如果是,请继续(不阻止)。如果不是,请等并且buildSentence()会在执行期间锁定信号量。但是,如果某人有一个直截了当的建议方法,我不想过度设计这个问题。让我知道!

1 个答案:

答案 0 :(得分:3)

我想我会选择这样的事情:

public class MySingleton 
    implements IConfigurationObserver {
    // Member(s)
    private static volatile MySingleton instance;
    private final Configuration configuration;
    private volatile ParsedConfiguration currentConfig;

    // Constructor(s)
    private MySingleton(Configuration configuration) {
        this.configuration = configuration;
        parseConfiguration();
        configuration.addObserver(this);
    }

    public static MySingleton getInstance(Configuration configuration) {
        // Perform synchronized creation if applicable (double locking technique)
        MySingleton syncInstance = instance;
        if (syncInstance == null) {
            synchronized(MySingleton.class) {
                syncInstance = instance; // Verify once again after synchronizing
                if(syncInstance == null) {  
                    syncInstance = new MySingleton(configuration);
                    instance = syncInstance;
                }
            }
        }

        return syncInstance;
    }

    // Private Method(s)
    private void parseConfiguration() {
        // Import values from the configuration
        currentConfig = configuration.getNewParsedConfiguration();
    }

    // Public Method(s)
    public String buildSentence() {
        // Build a new string based on values pulled from the current configuration
        ParsedConfiguration configInUse = currentConfig;
        StringBuilder strBuilder = new StringBuilder();
        strBuilder.append(configInUse.getFirstParam());
        strBuilder.append(" - ");
        strBuilder.append(configInUse.getSecondParam());

        return strBuilder.toString();
    }

    // Observer Method(s)
    @Override
    public void onConfigurationUpdated() {
        // The configuration has changed. Parse it again.
        parseConfiguration();
    }
}

请注意在currentConfig开头将buildSentence移动到本地变量的安全措施,以便对使用过的ParsedConfig的引用无法更改中间方法。< / p>