简体   繁体   中英

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.

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?

  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.

It won't work without synchronized . There would be no way to know the size of the table when tablePlayers.add(player) is called. 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. The thread A could test 8 < 9 (looks OK to me,) and add another player to the table. 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?

CHM is thread-safe but that only means that it protects itself from concurrent operations. Just because you are storing an object in a CHM doesn't then make the object magically thread-safe as well. 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.

In the case of your code, what happens if two threads call join at the same time?

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. 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.

Also, the synchronized block also protects the table-list itself which probably is not a synchronized collection itself. Without the synchronized block there, then the 2 threads could write to the List at the same time and corrupt it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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