简体   繁体   English

共享对象的线程安全更新而不会影响性能?

[英]Thread-safe update of shared object without performance penalty?

I'm struggling to wrap my head around making a thread-safe implementation of my class to be shared amongst Servlets or multiple 'clients' in the code. 我正在努力使我的类的线程安全实现可以在Servlet或代码中的多个“客户端”之间共享。

Pretend I have the following MySingleton class, a singleton that gets initialized with a Configuration object. 假设我有以下MySingleton类,该类是使用Configuration对象初始化的。 However, the Configuration object can be observed, and so the singleton will subscribe to be notified if changes are made. 但是,可以观察到Configuration对象,因此如果进行更改,单例将订阅通知。 Important key points: 重要要点:

  • The configuration can change at any time (impossible to predict) 该配置可以随时更改(无法预测)

  • When the configuration is parsed, its values are saved into member fields of MySingleton 解析配置后,其值将保存到MySingleton的成员字段中

  • The singleton's public method uses those fields to generate a return result 单例的public方法使用这些字段来生成返回结果

See simplified code below: 请参见下面的简化代码:

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();
    }
}

The class works fine on its own (in a single thread environment), but here's a few threats I want to eliminate in multithreaded scenarios: 该类自己可以正常工作(在单线程环境中),但是在多线程场景中,我想消除一些威胁:

  • If two updates are made to the Configuration in a very short time, it's possible that the first call to parseConfiguration() is not finished before the second call begins. 如果在很短的时间内对Configuration进行了两次更新,则可能在第二次调用开始之前没有完成对parseConfiguration()的第一次调用。 This one is easy to solve, I could just make parseConfiguration() synchronized (right?). 这很容易解决,我可以使parseConfiguration()同步(对吗?)。 But... 但...

  • Pretend that we are in the middle of a call to buildSentence() when the Configuration notifies our singleton. 假设当Configuration通知我们的单身人士时,我们正处于对buildSentence()的调用中间。 I don't want buildSentence() to use a mix of old values for firstParam and secondParam (ie if parseConfiguration() is halfway done). 我不希望buildSentence()为firstParam和secondParam使用旧值的混合(即,如果parseConfiguration()已完成一半)。 As such, I could put a synchronized block on the Configuration object for both parseConfiguration() and buildSentence() , but then I have a severe performance hit: I cannot have more than one concurrent call to buildSentence() . 这样,我可以在parseConfiguration()buildSentence()Configuration对象上放置一个同步块,但是这样会严重打击性能:我不能对buildSentence()进行多个并发调用。 In truth, the ideal situation for me is that: 实际上,对我来说理想的情况是:

    • If buildSentence() is running and a Configuration update occurs, parseConfiguration() has to wait until buildSentence() is over before running 如果buildSentence()正在运行并且发生Configuration更新,则parseConfiguration()必须等到buildSentence()结束后再运行

    • If parseConfiguration() is running, a call to buildSentence() must wait until parseConfiguration() is over before starting 如果parseConfiguration()正在运行,则对buildSentence()的调用必须等到parseConfiguration()结束后才能开始

    • However, once parseConfiguration() is finished, I want to allow multiple threads to run buildSentence() at the same time. 但是,一旦parseConfiguration()完成,我想允许多个线程同时运行buildSentence() Locking should only occur if an update is about to take place, or taking place. 只有在即将进行更新或正在进行更新时,才应进行锁定。

How can I refactor MySingleton to allow for the ideal 'rules' I've listed above? 我如何重构MySingleton以允许上面列出的理想“规则”? Is it possible? 可能吗?


I have been thinking of a solution which involves Semaphores. 我一直在想一个涉及信号量的解决方案。 ie: when performing buildSentence() , check if the semaphore is available. 即:在执行buildSentence() ,检查信号量是否可用。 If yes, go ahead (no blocking). 如果是,请继续(无阻塞)。 If no, wait. 如果否,请等待。 And parseConfiguration would lock the semaphore during its execution. 并且parseConfiguration将在信号执行期间锁定该信号。 However I don't want to over-engineer this problem if someone has a straightforward approach to suggest. 但是,如果有人有直接的建议方法,我不想过度设计这个问题。 Let me know! 让我知道!

I think I would go for something like this: 我想我会喜欢这样的东西:

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();
    }
}

Take note of the safeguard to move currentConfig to a local variable at the beginning of buildSentence so the reference to the used ParsedConfig can't change mid-method. 请注意在buildSentence开头将currentConfig移至局部变量的buildSentence ,以确保对使用的ParsedConfig的引用不能更改中间方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM