java - 跨线程和逻辑的同步

标签 java multithreading synchronization concurrenthashmap

我正在使用 JAVA 开发扑克游戏服务器,我想在我的游戏中添加重新连接功能。逻辑如下:

当玩家在玩游戏时断线时,他对应的数据对象将被保留,直到当前游戏结束。如果他可以在游戏结束前重新连接(重新连接),他就可以使用该数据对象继续玩游戏。

这是我的代码:

////// file: Game.java
import java.util.ArrayList;
import java.util.List;

class Game{
    private int id;

    //Maintain the list of ids of players who are playing in this game
    private List<Integer> gamePlayerIds = new ArrayList<>(9); //Max 9 players in a Poker table

    //reference back to main class
    private GameController mnt;

    public Game(int id, GameController mnt){
        this.id = id;
        this.mnt = mnt;
    }

    public void endGame(){
        //find players who are disconnected while playing and not yet reconnect
        for (Integer playerId : gamePlayerIds){
            GameController.Player player = mnt.getPlayerById(playerId);
            if (player != null && !player.isConnected()){
                //if found, remove player object from Hashmap to prevent Memory Leak
                mnt.removePlayerById(playerId);
            }
        }
    }
}

/////// file: GameController.java
import java.util.concurrent.ConcurrentHashMap;

public class GameController {

    private ConcurrentHashMap<Integer, Player> players;

    public Player getPlayerById(int id){
        return players.get(id);
    }

    public void removePlayerById(int id){
        players.remove(id);
    }

    public void addPlayer(Player player){
        players.putIfAbsent(player.getId(), player);
    }

    public GameController(){
        players = new ConcurrentHashMap<>();
        /* Do other initializations here */
    }
}

////////// file: Player.java
class Player{
    private int id;

    private boolean isConnected = true;
    private boolean isPlaying = false;

    public boolean isPlaying() {
        return isPlaying;
    }

    public void setPlaying(boolean playing) {
        isPlaying = playing;
    }

    public boolean isConnected() {
        return isConnected;
    }

    public void setConnected(boolean connected) {
        isConnected = connected;
    }

    public Player(int id){
        this.id = id;
    }

    public int getId(){
        return id;
    }
}

//////// file: OnConnectEventHandler.java
class OnConnectEventHandler {

    //reference back to main class
    GameController mnt;

    public OnConnectEventHandler(GameController mnt){
        this.mnt = mnt;
    }

    /*
    * Handle event when a connection is made. There're 3 cases:
    * 1. New connection
    * 2. Duplicated connection (already connect before, and not yet disconnect
    * 3. Reconnect
    */
    public void handleEvent(User user){

        Player player = mnt.getPlayerById(user.getId());

        if (player == null){
            //New connection, then convert User to Player and add 
            //new player object to ConcurrentHashMap which maintains the list
            //of online players
            mnt.addPlayer(new Player(user.getId()));
        } else {
            if (player.isConnected()){
                //TODO: Alert error because of duplicated login
                return;
            } else {
                //set connected flag to true, so that the endGame function
                //will not remove this reconnected user
                player.setConnected(true);
            }
        }
    }
}

///// file: OnDisconnectEventHandler 
class OnDisconnectEventHandler {

    //reference back to main class
    GameController mnt;

    public OnDisconnectEventHandler(GameController mnt){
        this.mnt = mnt;
    }

    /*
    Handle disconnect event, there are 2 cases:
    1. Disconnected player is not playing, so remove its data immediately
    2. Disconnected player is playing, so keep its data until the end of current game
       so that if he reconnect before the game ends, he can continue to play that game
    */
    public void handleEvent(User user){
        Player player = mnt.getPlayerById(user.getId());
        if (player != null){
            if (player.isPlaying()){
                //if player is disconnected while playing, just marked that he is disconnect instead of
                //removing Player object immediately
                player.setConnected(false);
            } else {
                //if player is not playing, remove Player object immediately to prevent memory leak
                mnt.removePlayerById(user.getId());
            }
        }
    }
}
  • 我使用 ConcurrentHashMap 来维护在线玩家列表。

  • 当玩家在玩游戏时断开连接时,我将该玩家的“disconnected”标志设置为 true(isDisconnected 是 Player 类的 boolean 属性),而不是从 Players HashMap 中删除玩家对象。

  • 当当前游戏结束时,检查该游戏中的所有玩家,如果玩家的断开连接标志设置为 true,则从 Players hashmap 中删除该玩家以防止内存泄漏。 (1)

  • 当玩家连接时,我检查玩家对象是否存在于玩家 HashMap 中。如果存在,并且玩家的断开连接标志设置为 true,那么我将该标志设置为 false,以便 (1) 中的步骤不会删除玩家的数据对象。 (2)

我的实现在大多数情况下工作正常,但这里有一个问题: 在多线程环境下,代码可以按以下顺序执行:

  • (2):从Players hashmap中获取已存在的玩家对象。

  • (1):玩家断开连接标志仍然为 true,从玩家 HashMap 中删除玩家对象。

  • (2):玩家断开连接标志设置为 false;玩家对象仅从 (2) 的线程内存中引用。当(2)完成后,引用消失,玩家对象被GC过程清除。

  • 因此,播放器已连接,但程序内存中没有存储播放器数据。

我应该怎么做才能解决这个问题?

提前谢谢您!

更新 1 我可以像这样添加同步块(synchronized block)吗:

//in OnConnectEventHandler class
public void handleEvent(User user){

    synchronized(mnt.players){
        Player player = mnt.getPlayerById(user.getId());

        if (player == null){
            //New connection, then convert User to Player and add 
            //new player object to ConcurrentHashMap which maintains the list
            //of online players
            mnt.addPlayer(new Player(user.getId()));
        } else {
            if (player.isConnected()){
                //TODO: Alert error because of duplicated login
                return;
            } else {
                //set connected flag to true, so that the endGame function
                //will not remove this reconnected user
                player.setConnected(true);
            }
        }
    }
}

// in OnDisconnectEventHandler class
public void handleEvent(User user){
    synchronized(mnt.players){
        Player player = mnt.getPlayerById(user.getId());
        if (player != null){
            if (player.isPlaying()){
                //if player is disconnected while playing, just marked that he is disconnect instead of
                //removing Player object immediately
                player.setConnected(false);
            } else {
                //if player is not playing, remove Player object immediately to prevent memory leak
                mnt.removePlayerById(user.getId());
            }
        }
    }
}

如果有效,是否会导致性能问题?我的游戏是多人游戏,通常有3k-5k CCU,在最拥挤的时候,每秒大约有300个连接/断开事件。

更新2 我使用单独的线程来处理连接和断开事件。如果玩家在当前游戏结束的同时重新连接,就会发生我问题中的情况。

当玩家不在游戏中时(例如在大厅、私有(private)房间......),他可以断开连接。在这些情况下,因为玩家没有执行“渐进”和“重要” Action ,所以我不需要实现重新连接功能。

最佳答案

(1) 和 (2) 需要在players map 上进行同步,以便它们中的任何一个在获取锁后都能看到一致的玩家 map View 和玩家状态。

关于java - 跨线程和逻辑的同步,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45421965/

相关文章:

java - 读取文件最大和最小数量

java - Java 中的同步多线程(Apache HTTPClient)

java - 在文件中的每个单词后添加空格 (Java)

java - 如何根据优先级用元素填充数组?

c - 函数调用是现代平台的有效内存屏障吗?

multithreading - 多核 + 超线程 - 线程是如何分布的?

java - 与线程同步

javascript - jquery ajax async false 不起作用

java - 如何使用 Java 创建多语言 Alexa Skill?

c# - Xamarin IOS WebClient 在线程中执行