Java 线程安全,无需在可变状态上同步

Java 线程安全,无需在可变状态上同步,java,multithreading,Java,Multithreading,我有3个读方法和1个写方法的类: class ResourceClass { private static Map resourceMap = new HashMap(); // New method to update resource public void write(String key, Object resource) { resourceMap.put(key, resource); } public Object read(String var1

我有3个读方法和1个写方法的类:

class ResourceClass {

  private static Map resourceMap = new HashMap();

  // New method to update resource
  public void write(String key, Object resource) {
    resourceMap.put(key, resource);
  }

  public Object read(String var1) {
    return resourceMap.get(var1);
  }

  public Object read(String var1, String var2) {
    // .. Do task with var1 and var2
    return resourceMap.get(var1);
  }

  public Object read(String var1, String var2, String var3) {
    // .. Do task with var1, var2 and var3
    return resourceMap.get(var1);
  }  
}
目前,这个类只包含一个write和3个read方法来消耗静态资源。此配置的问题在于更新
resourceMap
的唯一方法是重新启动应用程序,以便再次创建
ResourceClass
,并将
resourceMap
添加到类中供其使用

我想添加一些动态方法来更新
resourceMap
,而无需重新启动服务,但为此,我必须使此类线程安全,以便处理
write
方法来安全地更新
resourceMap
。为此,我可以选择在
read
write
方法中使用
synchronized
关键字,这样只有一个线程可以访问
resourceMap
。这种方法解决了问题,但也包括其他方法。这些
read
方法是高度并发的方法,因此添加
synchronized
关键字将显著影响服务性能,我们当然不希望这样


是否有人知道如何保持线程读取(不相互阻塞),但当有一个线程执行
写入时,所有
读取
方法都会等待写入完成,并在
写入
完成时恢复?

对于高读取重载,考虑克隆克隆并将其添加到克隆中,然后将结果分配给字段。
如果可以并发调用write方法,则必须确保对该方法的访问是“线程安全的”。

正如@Mike Mnomonic在评论中所说的,是一个线程安全的映射,具有可调的并发级别。与其他哈希映射一样,
ConcurrentHashMap
有一个后备数组;根据指定的并发级别(默认值16),此备用阵列被拆分为多个子阵列,每个子阵列有一个锁,例如,如果您的容量为128,并且使用默认并发级别16,则子阵列[0,8]有自己的锁,[8,16]有自己的锁,[16,24)有自己的锁,以此类推,因此两个线程可以写入两个不同的子数组,而不会相互阻塞

如果写入非常罕见,那么使用包装在中的可能会获得更好的性能

私有最终原子参考资源图;
公共无效写入(字符串键、对象值){
布尔成功=假;
而(!成功){
ImmutableMap oldMap=resourceMap;
ImmutableMap.Builder=ImmutableMap.Builder();
builder.putAll(resourceMap.entrySet());
builder.put(键、值);
success=resourceMap.compareAndSet(oldMap,builder.build());
}
}
公共对象读取(字符串var1){
...
返回resourceMap.get().get(var1);//获取映射,然后获取值
}
公共对象读取(字符串var1,字符串var2);
公共对象读取(字符串var1、字符串var2、字符串var3);

write
上,您正在复制旧地图,添加新值,并将新地图替换为旧地图;如果两个更新尝试互相践踏,则只有一个更新会成功,同时另一个将继续重试,直到其更新粘贴为止。读者无需关心任何这些-他们只需
获取当前地图

你研究过吗?顺便说一句,
resourceMap
是静态的,但是它被实例方法分配/读取/修改,这似乎是错误的。我不得不想象你将这个类作为一种单例来使用,但是设计没有强制执行,所以我觉得它看起来是坏的。当你实例化时,
resourceMap
会发生什么第二次ate
ResourceClass
?好吧,它实际上没有在构造函数中赋值,它只有一个
addResource()
方法,该方法仅在服务初始化期间调用。
ResourceClass
从未实例化。顺便说一句,只要阅读有关
ReadWriteLock
的内容,并且看起来正是我想要的,我将尝试一下,看看它的性能如何。您可以使用a作为背景映射。您的意思是,“
ResourceClass
从未实例化"?您展示的方法都是实例方法——如果不实例化它们,就无法调用它们!我认为您的意思是,此策略将避免同步读取器。但为了确保读取器在重新分配后看到新的克隆集合,仍然需要某种形式的内存同步。@sstan yep-这是比率onale支持这种方法。不需要同步-规范保证它(在CPU重新排序读写之外-但在本例中这不会产生一定的负面影响)。也许您可以共享您所引用的规范部分。我的理解是,每个线程都有自己的内存副本,对于读线程来说,要想看到写入线程所做的更改,需要一些同步或内存障碍。否则,读线程可能会无限期地从过期的内存副本中读取@sstan抱歉-您是对的,可能存在这样一种情况,即值永远不会在线程之间同步(假设在线程上命中零个同步块)。正如JoeAlmore指出的,将字段标记为volatile就足够了,或者,使用一个预烘焙的写时拷贝集合实现可能是一个很好的方法。谢谢你的回答。
ConcurrentHashMap
的理论很有趣。然而,我必须补充一点,实际上写操作非常罕见,因为它只有当向服务添加新模块时出现,而不是在服务运行期间出现。@JoeAlmore请参阅我关于使用
volatile ImmutableMap
的更新答案,它应该以较差的写入性能为代价提供良好的读取性能。我同意如果更新非常不及时,则
ConcurrentHashMap
是不合适的ent.Re:“
volatile
携带
private final AtomicReference<ImmutableMap<String, Object>> resourceMap;

public void write(String key, Object value) {
    boolean success = false;
    while(!success) {
        ImmutableMap oldMap = resourceMap;
        ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
        builder.putAll(resourceMap.entrySet());
        builder.put(key, value);
        success = resourceMap.compareAndSet(oldMap, builder.build());
    }
}

public Object read(String var1) {
    ...
    return resourceMap.get().get(var1); // get map, then get value
}
public Object read(String var1, String var2);
public Object read(String var1, String var2, String var3);