[英]Are values of ConcurrentHashMap being synchronized by default?
下面是我在這里找到的一段代碼。 它模仿一個在線游戲服務器,玩家可以在其中加入桌子。
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是ConcurrentHashMap的值之一。
List<Player> tablePlayers = tables.get(table.getId());
既然ConcurrentHashMap已經是線程安全的,為什么要使用它時還需要同步其value對象?
synchronized (tablePlayers) {
...
}
因為在程序中使用線程安全的對象不會使程序成為線程安全的。
有問題的代碼在這里:
synchronized (tablePlayers) {
if (tablePlayers.size() < 9) {
tablePlayers.add(player);
}
}
顯然,打算將桌子的大小限制為不超過9個玩家。
沒有synchronized
就無法工作。 調用tablePlayers.add(player)
時,將無法知道表的大小。 問題是,線程A可以調用tablePlayers.size()
,並獲取數字8。然后線程B可以將玩家添加到表中。 線程A可以測試8 < 9
(在我看來不錯),然后向表中添加另一個玩家。 結果是桌子上有十個玩家。 不是作者的意圖。
該表是“線程安全的”這一事實僅意味着該表自身的內部數據不會在多個線程訪問它時被破壞。 這並不意味着該方案將永遠做正確的事與數據。
既然ConcurrentHashMap已經是線程安全的,為什么要使用它時還需要同步其value對象?
CHM 是線程安全的,但這僅意味着它可以保護自己免受並行操作的影響。 僅僅因為您將對象存儲在CHM中並不能使該對象也具有神奇的線程安全性。 從CHM中檢索表列表后,如果要由多個線程訪問該對象,則可以適當地鎖定該對象。
就您的代碼而言,如果兩個線程同時調用join,會發生什么?
synchronized (tablePlayers) {
// race condition here because a test...
if (tablePlayers.size() < 9) {
// and then the add method
tablePlayers.add(player);
}
}
如果沒有synchronized
塊,則這兩個線程可能都同時檢查,並且看到表的大小為8(例如),然后兩個都將自己添加到表中。 這就是比賽條件的全部。 因為有2個操作(一個測試,然后附加)時, synchronized
是必要的,以確保只有1在一個時間的線程可以看到,如果它們能夠加入一個表,然后加入。
另外, synchronized
塊還保護表列表本身,而表列表本身可能不是synchronized
集合。 如果沒有synchronized
塊,則這兩個線程可以同時寫入List
並破壞List
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.