Java 不变性和可重新加载配置

Java 不变性和可重新加载配置,java,multithreading,concurrency,thread-safety,immutability,Java,Multithreading,Concurrency,Thread Safety,Immutability,请注意:尽管这个问题提到了Java,但我认为这本质上是一个OOP/并发问题,任何具有丰富编程经验的人都可能回答这个问题 因此,我正在构建一个ConfigurationLoader,它将从远程服务读取ConfigurationPOJO,并通过API使其可用。有几件事: 一旦ConfigurationLoader第一次被要求进行配置,后台线程(工作线程)将每隔30秒ping远程服务一次,以进行更新,然后将这些更新应用于配置实例;及 如果配置被修改,后台工作人员将收到更改通知,并将“新的”配置推送

请注意:尽管这个问题提到了Java,但我认为这本质上是一个OOP/并发问题,任何具有丰富编程经验的人都可能回答这个问题


因此,我正在构建一个
ConfigurationLoader
,它将从远程服务读取
Configuration
POJO,并通过API使其可用。有几件事:

  • 一旦
    ConfigurationLoader
    第一次被要求进行
    配置
    ,后台线程(工作线程)将每隔30秒ping远程服务一次,以进行更新,然后将这些更新应用于
    配置
    实例;及
  • 如果
    配置
    被修改,后台工作人员将收到更改通知,并将“新的”
    配置
    推送到远程服务
  • ConfigurationLoader
    Configuration
    都必须是线程安全的
所以当我想到“线程安全性”时,我想到的第一件事就是不变性,这将引导我走向优秀的项目,比如。问题是,
配置
不能是不变的,因为我们需要能够在客户端对其进行更改,然后让加载程序将这些更改反馈给服务器

我的下一个想法是尝试使
ConfigurationLoader
Configuration
都成为单例,但问题是
ConfigurationLoader
需要很多参数来实例化它,因此,在构造中接受参数的单例并不是真正的单例

// Groovy pseudo-code
class Configuration {
    // Immutable? Singleton? Other?
}

class ConfigurationLoader {
    // private fields
    Configuration configuration

    ConfigurationLoader(int fizz, boolean buzz, Foo foo, List<Bar> bars) {
        super()

        this.fizz = fizz
        this.buzz = buzz;
        // etc.
    }

    Configuration loadConfiguration() {
        if(configuration == null) {
            // Create background worker that will constantly update
            // 'configuration'
        }
    }
}
//Groovy伪代码
类配置{
//不可变?单例?其他?
}
类配置加载器{
//私人领域
配置
ConfigurationLoader(int-fizz、boolean-buzz、Foo-Foo、列表栏){
超级()
this.fizz=fizz
this.buzz=buzz;
//等等。
}
配置loadConfiguration(){
if(配置==null){
//创建将不断更新的后台工作程序
//“配置”
}
}
}

我的选项是什么?如何创建线程安全的加载程序和配置,其中配置可以由客户端(按需)更改,也可以由后台工作线程异步更改?

您需要一个单例来实现这一点,但单例并不是不变的。这是线程安全的东西。使您的单例(配置)包含一个简单的属性对象或其他内容,并通过同步保护对此对象的访问。您的配置加载器不知怎的知道这个配置单例,并且在同步下,当它检测到更改时,可以设置Properties对象的新实例

我很确定ApacheCommons配置会执行类似的操作

问题是配置不能是不变的,因为我们需要能够更改它

它仍然是不可变的,您只需为每个更改创建一个新的(“写时复制”)

我的选择是什么

首先要考虑的是:在并发运行的任务中,您希望如何对配置更改做出反应?基本上,您有三种选择:

忽略配置更改,直到任务完成

例如,代码将文件写入的某个目录-将当前文件写入当前目标目录后,将新文件放入新目录。如果您从未创建过该文件,那么将一些字节写入
/new/path/somefile
将不是一个好主意。最好的选择可能是将一个不可变的
配置
对象存储在任务实例的字段中(即,在任务创建时-在这种情况下,为了清晰起见,您还可以将该字段设置为final)。如果您的代码被设计为一组孤立的小任务,那么这通常效果最好

优点:配置永远不会在一个任务中改变,所以这很容易得到安全和易于测试

缺点:配置更新不会使其成为已经运行的任务

让您的任务检查配置更改

也就是说,您的任务定期向电子邮件地址发送一些数据。为您的配置(如伪代码)提供一个中央存储,并在某个时间间隔(即从收集数据到发送邮件之间)从任务代码中重新获取它。这通常最适用于长时间运行/永久性任务

优点:配置可以在任务运行期间更改,但仍然有点简单,以确保安全-只需确保您有一些用于读取配置的内存屏障(使您的私有
配置
字段
易失性
,使用
原子引用
,用锁保护它,等等)

缺点:任务代码比第一个选项更难测试。检查之间的配置值可能仍然过期

任务的信号配置更改

基本上是方案二,但反过来说。每当配置更改时,中断您的任务,将中断处理为“配置需要更新”消息,继续/重新启动新配置

优点:配置值永远不会过时

缺点:这是最难做到的。有些人甚至可能会争辩说,您无法正确执行此操作,因为中断只应用于任务中止。如果您将任务的更新检查放在正确的位置,那么与第二个选项相比,只有很小的好处(如果有的话)。如果你没有充分的理由,就不要这样做