【问题标题】:Synchronized (Hash)Map of Singletons单例的同步(哈希)映射
【发布时间】:2016-01-18 15:04:58
【问题描述】:

代码下方的说明...

// Singleton
public static final Map<String, Account> SHARED_ACCOUNT_HASHMAP =
        Collections.synchronizedMap(new HashMap<>());


public init(String[] credentials) {

    Account account = null;     

    String uniqueID = uniqueAccountIdentifier(credentials);

    if (SHARED_ACCOUNT_HASHMAP.containsKey(uniqueID)) {
        account = SHARED_ACCOUNT_HASHMAP.get(uniqueID);
        log("...retrieved Shared Account object: %s", uniqueID);
    }

    // create the Account object (if necessary)
    if (account == null) {
        account = new Account(credentials);

        // Store it in the SHARED_ACCOUNT_HASHMAP
        SHARED_ACCOUNT_HASHMAP.put(uniqueID, account);
        log("...created Account object: %s",uniqueID);

    }

}

我想要达到的目标

  • 有多个线程访问此 Singleton HashMap
  • 此 HashMap 的目标是只允许为每个 uniqueID 创建一个帐户
  • 后面的账号可以被各个线程取回,进行账号操作
  • 每个线程都有这个init() 方法并运行一次。
  • 因此,第一个找不到现有 uniqueID 帐户的线程创建一个新帐户并将其放置在 HashMap 中。下一个线程发现对于相同的 uniqueID,已经有一个 Account 对象 - 所以稍后检索它以供自己使用

我的问题...

  • 如何让其他线程(第二个、第三个等)在第一个线程插入新帐户对象时等待?
  • 换一种说法,在读取同一个 uniqueID 键的 HashMap 时,永远不应该有 2 个线程收到 null 值。第一个线程可能会收到 null 值,但第二个线程应该检索第一个放置在那里的 Account 对象。

【问题讨论】:

    标签: java multithreading collections hashmap synchronization


    【解决方案1】:

    根据synchronizedMap() 的文档

    返回由指定映射支持的同步(线程安全)映射。为了保证串行访问,所有对 backing map 的访问都必须通过返回的 map 完成。

    当迭代其任何集合视图时,用户必须在返回的地图上手动同步

    换句话说,您仍然需要拥有synchronizedSHARED_ACCOUNT_HASHMAP 的访问权限:

    public init(String[] credentials) {
        Account account = null;     
        String uniqueID = uniqueAccountIdentifier(credentials);
    
        synchronized (SHARED_ACCOUNT_HASHMAP) {
            if (SHARED_ACCOUNT_HASHMAP.containsKey(uniqueID)) {
                account = SHARED_ACCOUNT_HASHMAP.get(uniqueID);
                log("...retrieved Shared Account object: %s", uniqueID);
            }
    
            // create the Account object (if necessary)
            if (account == null) {
                account = new Account(credentials);
    
                // Store it in the SHARED_ACCOUNT_HASHMAP
                SHARED_ACCOUNT_HASHMAP.put(uniqueID, account);
                log("...created Account object: %s",uniqueID);
            }
        }
    }
    

    【讨论】:

    • 啊,我现在明白了——谢谢。我确实阅读了有关迭代器的教程,但是我只是忽略了这一点,因为我不需要使用迭代器。
    • 通过这个实现,您不再需要使用同步映射,因为一次只有一个线程会访问它(用于读取或写入)。
    【解决方案2】:

    如果您有多个读取器/写入器,请考虑使用 ReadWriteLock(请参阅 ReadWriteLock example)。

    通常ConcurrentHashMap 的性能优于您使用的同步哈希映射。

    【讨论】:

    • 我对@9​​87654324@ 的工作原理有所了解,但对于我需要的东西来说这听起来有点矫枉过正。我会阅读 ReadWriteLock - 可能它对我的应用程序的其他部分会有所帮助。按照 epoch 的建议在 hashmap 上同步,这是我在这里寻找的。​​span>
    • ReadWriteLock 的性能将优于 epoch 的解决方案,但它确实需要更多的代码。
    • 如果我使用了ConcurrentHashMap,这个synchronized(on the hashmap) 块还有必要吗?我想是的,对吧?如果我不以这种方式同步,我的代码将无法工作 - 即使使用 ConcurrentHashMap
    • 您希望将两个操作作为一个原子调用进行。在这种情况下,您必须使用同步或特殊方法,例如 docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/…
    【解决方案3】:

    在以下代码中,当您尝试在同步映射(containsKeyget)上执行两个操作时,我可以感觉到竞争条件 check-then-act 的味道:

    if (SHARED_ACCOUNT_HASHMAP.containsKey(uniqueID)) {
            account = SHARED_ACCOUNT_HASHMAP.get(uniqueID);
            log("...retrieved Shared Account object: %s", uniqueID);
     }
    

    因此,为了避免竞争条件,您需要在此地图上同步:

    synchronized (synchronizedMap) {
      if (SHARED_ACCOUNT_HASHMAP.containsKey(uniqueID)) {
                account = SHARED_ACCOUNT_HASHMAP.get(uniqueID);
                log("...retrieved Shared Account object: %s", uniqueID);
         }
      // rest of the code.      
    }
    

    实际上,synchronizedMap 可以保护自己免受可能破坏地图数据的内部竞争条件的影响,但对于外部条件(如上),您需要注意这一点。如果你觉得你在很多地方都在使用synchronized 块,你也可以考虑使用常规地图和同步块。你会发现这个question 也很有用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-05
      • 2018-10-13
      • 2015-09-08
      • 2016-08-04
      相关资源
      最近更新 更多