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

a1e*_*x07 3 java multithreading synchronization

我正在写一个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;
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

所以我有几个问题.
1.假设属性是只读的,我必须使用synchronizegetProperty
2.是否有意义做this.key_values = Collections.synchronizedMap(tmp_map)reloadConfig

谢谢.

Bru*_*eis 5

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

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

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

首先,自从你reloadConfig()公开之后,你的对象看起来并不真实.如果对象确实是不可变的,也就是说,如果在其值初始化之后它们不能改变(这是在共享的对象中具有的期望属性).

由于上述原因,您必须同步对映射的所有访问权限:假设一个线程正在尝试从另一个线程调用时读取它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) { ... } }
}
Run Code Online (Sandbox Code Playgroud)

这里的正确发布final关键字保证.它很微妙:它保证了在初始化之后每个线程都可以看到这个字段的值(没有它,一个线程可能会看到它lock == null,并且会发生坏事).

您可以使用(正确发布)改进上面的代码ReadWriteReentrantLock.如果您担心这一点,它可能会提高并发性.

假设您的目的是创建MyDBConfig不可变的,您不需要序列化对哈希映射的访问(也就是说,您不一定需要添加synchronized关键字).您可以改善并发性.

首先,make 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(); }
}
Run Code Online (Sandbox Code Playgroud)

volatile关键字是非常微妙的.易失性写入和易失性读取具有发生在之前的关系.据说易失性写入发生在相同(易失性)字段的后续易失性读取之前.这意味着,在执行相同(易失性)字段的子序列易失性读取之后,所有已经(在程序顺序中)被修改的所有存储器位置对于每个其他线程都是可见的.

在上面的代码中,您在设置了所有值写入truevolatile字段.然后,读取值()的方法开始于执行相同字段的易失性读取.然后,此方法可以保证看到正确的值.getProperty(...)

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