简体   繁体   English

默认情况下是否同步ConcurrentHashMap的值?

[英]Are values of ConcurrentHashMap being synchronized by default?

Below is a piece of code I found here . 下面是我在这里找到的一段代码。 It mimics an online gameserver, where players can join to the tables. 它模仿一个在线游戏服务器,玩家可以在其中加入桌子。

public class GameServer {
  public Map<String, List<Player>> tables = new ConcurrentHashMap<String, List<Player>>();

  public void join(Player player, Table table) {
    if (player.getAccountBalance() > table.getLimit()) {
      List<Player> tablePlayers = tables.get(table.getId());

      synchronized (tablePlayers) {
        if (tablePlayers.size() < 9) {
          tablePlayers.add(player);
        }
      }
    }
  }

  public void leave(Player player, Table table) {/*Method body skipped for brevity*/}

  public void createTable() {
    Table table = new Table();
    tables.put(table.getId(), table);
  }

  public void destroyTable(Table table) {
    tables.remove(table.getId());
  }
}

tablePlayers is one of the value of ConcurrentHashMap. tablePlayers是ConcurrentHashMap的值之一。

List<Player> tablePlayers = tables.get(table.getId());

Now that ConcurrentHashMap is already thread-safe, why do we still need to synchronize its value object when we want to use it? 既然ConcurrentHashMap已经是线程安全的,为什么要使用它时还需要同步其value对象?

  synchronized (tablePlayers) {
    ...
  }

Because using thread-safe objects in your program won't make your program thread-safe. 因为在程序中使用线程安全的对象不会使程序成为线程安全的。

The code in question is here: 有问题的代码在这里:

  synchronized (tablePlayers) {
    if (tablePlayers.size() < 9) {
      tablePlayers.add(player);
    }
  }

Obviously, it is intented to limit the size of the table to no more than nine players. 显然,打算将桌子的大小限制为不超过9个玩家。

It won't work without synchronized . 没有synchronized就无法工作。 There would be no way to know the size of the table when tablePlayers.add(player) is called. 调用tablePlayers.add(player)时,将无法知道表的大小。 The problem is, thread A could call tablePlayers.size() , and get back the number 8. Then thread B could add a player to the table. 问题是,线程A可以调用tablePlayers.size() ,并获取数字8。然后线程B可以将玩家添加到表中。 The thread A could test 8 < 9 (looks OK to me,) and add another player to the table. 线程A可以测试8 < 9 (在我看来不错),然后向表中添加另一个玩家。 The result would be ten players in the table. 结果是桌子上有十个玩家。 Not what the author intended. 不是作者的意图。

The fact that the table is "thread-safe" only means that the table's own internal data won't be corrupted when multiple threads access it. 该表是“线程安全的”这一事实仅意味着该表自身的内部数据不会在多个线程访问它时被破坏。 It does not mean that the program will always do the right thing with the data. 并不意味着该方案将永远做正确的事与数据。

Now that ConcurrentHashMap is already thread-safe, why do we still need to synchronize its value object when we want to use it? 既然ConcurrentHashMap已经是线程安全的,为什么要使用它时还需要同步其value对象?

CHM is thread-safe but that only means that it protects itself from concurrent operations. CHM 线程安全的,但这仅意味着它可以保护自己免受并行操作的影响。 Just because you are storing an object in a CHM doesn't then make the object magically thread-safe as well. 仅仅因为您将对象存储在CHM中并不能使该对象也具有神奇的线程安全性。 Once you retrieve the table-list from the CHM it is up to you to lock the object appropriately if it is going to be accessed by multiple threads. 从CHM中检索表列表后,如果要由多个线程访问该对象,则可以适当地锁定该对象。

In the case of your code, what happens if two threads call join at the same time? 就您的代码而言,如果两个线程同时调用join,会发生什么?

synchronized (tablePlayers) {
   // race condition here because a test...
   if (tablePlayers.size() < 9) {
      // and then the add method
      tablePlayers.add(player);
   }
}

If there was not the synchronized block then the 2 threads might both check at the same time and see that the size of the table was 8 (for example) and then both go to add themselves to the table. 如果没有synchronized块,则这两个线程可能都同时检查,并且看到表的大小为8(例如),然后两个都将自己添加到表中。 That's what a race condition is all about. 这就是比赛条件的全部。 Because there are 2 operations (a test and then an add), the synchronized is necessary to ensure that only 1 threads at a time can see if they can join a table and then join it. 因为有2个操作(一个测试,然后附加)时, synchronized是必要的,以确保只有1在一个时间的线程可以看到,如果它们能够加入一个表然后加入。

Also, the synchronized block also protects the table-list itself which probably is not a synchronized collection itself. 另外, synchronized块还保护表列表本身,而表列表本身可能不是synchronized集合。 Without the synchronized block there, then the 2 threads could write to the List at the same time and corrupt it. 如果没有synchronized块,则这两个线程可以同时写入List并破坏List

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM