Java 线程安全,无需在可变状态上同步
我有3个读方法和1个写方法的类: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
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
会发生什么第二次ateResourceClass
?好吧,它实际上没有在构造函数中赋值,它只有一个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);