gpt4 book ai didi

java - 在 Java 中同步访问只读映射的正确方法

转载 作者:塔克拉玛干 更新时间:2023-11-01 22:32:42 26 4
gpt4 key购买 nike

我正在编写一个类似DatabaseConfiguration 类的类,它从数据库中读取配置,我需要一些关于同步的建议。例如,

public class MyDBConfiguration{
private Connection cn;
private String table_name;
private Map<String, String> key_values = new HashMap<String,String>();
public MyDBConfiguration (Connection cn, String table_name) {
this.cn = cn;
this.table_name = table_name;
reloadConfig();
}
public String getProperty(String key){
return this.key_values.get(key);
}
public void reloadConfig() {
Map<String, String> tmp_map = new HashMap<String,String> ();
// read data from database
synchronized(this.key_values)
{
this.key_values = tmp_map;
}
}
}

所以我有几个问题。
1. 假设属性是只读的,我是否在 getProperty 中使用了 synchronize
2. 在 reloadConfig 中执行 this.key_values = Collections.synchronizedMap(tmp_map) 是否有意义?

谢谢。

最佳答案

如果多个线程要共享一个实例,您必须使用某种同步。

需要同步主要有两个原因:

  • 可以保证一些操作是原子的,所以系统会保持一致
  • 它保证每个线程在内存中看到相同的值

首先,由于您将 reloadConfig() 公开,您的对象看起来并不是不可变的。如果对象真的是不可变的,也就是说,如果在初始化其值后它们不能更改(这是共享对象中期望的属性)。

由于上述原因,您必须同步对 map 的所有访问:假设一个线程正在尝试读取它,而另一个线程正在调用 reloadConfig()。坏事会发生。

如果情况确实如此(可变设置),您必须同步读取和写入(原因很明显)。 线程必须在单个对象上同步(否则没有同步)。保证所有线程在同一个对象上同步的唯一方法是在对象本身上或在正确发布、共享的锁上同步,如下所示:

// synchronizes on the in instance itself:
class MyDBConfig1 {
// ...
public synchronized String getProperty(...) { ... }
public synchronized reloadConfig() { ... }
}

// synchronizes on a properly published, shared lock:
class MyDBConfig2 {
private final Object lock = new Object();
public String getProperty(...) { synchronized(lock) { ... } }
public reloadConfig() { synchronized(lock) { ... } }
}

此处的正确发布final 关键字保证。它很微妙:它保证这个字段的值在初始化后对每个线程都是可见的(没有它,线程可能会看到 lock == null,并且会发生不好的事情)。

您可以使用(正确发布的)ReadWriteReentrantLock 改进上面的代码。如果您担心的话,它可能会稍微提高并发性。

假设您的意图是使 MyDBConfig 不可变,您不需要序列化对 HashMap 的访问(也就是说,您不一定需要添加 synchronized 关键字)。您可能会提高并发性。

首先,将reloadConfig()设为私有(private)(这将表明,对于该对象的消费者而言,它确实是不可变的:他们看到的唯一方法是getProperty(... ),根据其名称,不应修改实例)。

然后,您只需要保证每个线程都能在散列映射中看到正确的值。为此,您可以使用上面介绍的相同技术,或者可以使用 volatile 字段,如下所示:

class MyDBConfig {
private volatile boolean initialized = false;
public String getProperty(...) { if (initialized) { ... } else { throw ... } }
private void reloadConfig() { ...; initialized = true; }
public MyDBConfig(...) { ...; reloadConfig(); }
}

volatile 关键字非常微妙。 volatile 写入和 volatile 读取具有happens-before 关系。据说 volatile 写入先于同一( volatile )字段的后续 volatile 读取。这意味着在 volatile 写入之前(按程序顺序)修改过的所有内存位置在其他线程执行相同( volatile )字段的后续 volatile 读取后对所有其他线程可见。

在上面的代码中,您将true 写入volatile 字段之后 所有值都已设置。然后,读取值的方法 (getProperty(...)) 首先执行同一字段的可变读取。那么这个方法就可以保证看到正确的值。

在上面的例子中,如果你不在构造函数完成之前发布实例,那么可以保证在 getProperty(...) 方法中不会抛出异常(因为在构造函数完成之前,您已将 true 写入已初始化)。

关于java - 在 Java 中同步访问只读映射的正确方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8095818/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com