以下是我发现here的一段代码。 它模仿在线游戏服务器,玩家可以加入桌面。
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已经是线程安全的,为什么我们仍然需要在我们想要使用它时同步它的值对象?
synchronized (tablePlayers) {
...
}
答案 0 :(得分:0)
因为在程序中使用线程安全对象不会使程序成为线程安全的。
有问题的代码在这里:
synchronized (tablePlayers) {
if (tablePlayers.size() < 9) {
tablePlayers.add(player);
}
}
显然,它希望将桌子的大小限制为不超过9名玩家。
没有synchronized
,它就无法工作。调用tablePlayers.add(player)
时无法知道表的大小。问题是,线程A可以调用tablePlayers.size()
,然后返回数字8.然后线程B可以将一个玩家添加到表中。线程A可以测试8 < 9
(看起来对我好),并将另一个玩家添加到表中。结果将是表中的十名玩家。不是作者的意图。
表是&#34;线程安全&#34;仅表示当多个线程访问表时,表的内部数据不会被破坏。 不意味着程序将始终使用数据做正确的事情。
答案 1 :(得分:0)
现在ConcurrentHashMap已经是线程安全的,为什么我们还想在使用它时同步它的值对象呢?
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
块,则2个线程可能同时检查并看到表的大小为8(例如),然后两者都将自己添加到表中。这就是race condition的全部意义所在。因为有2个操作(测试然后是添加),所以synchronized
是必要的,以确保一次只有1个线程可以看到他们是否可以加入表和然后加入它
此外,synchronized
块还保护表列表本身,这可能不是synchronized
集合本身。如果没有synchronized
块,那么2个线程可以同时写入List
并破坏它。