Java 跨线程和逻辑的同步

Java 跨线程和逻辑的同步,java,multithreading,synchronization,concurrenthashmap,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

我正在使用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());
            }
        }
    }
}
如果有效,是否会导致性能问题?我的游戏是一个多人游戏,通常有3k-5k CCU,在最拥挤的时间,每秒大约有300个连接/断开事件

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


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

(1)和(2)需要在
玩家
地图上同步,以便在获得锁后,其中任何一个都可以看到玩家地图和玩家状态的一致视图。

如果需要以原子方式执行这些操作,最简单的解决方案是使用
synchronized
块-确保在所有线程中使用相同的监视器。如果仅在游戏结束时移除玩家,则无法达到所述场景。你能解释一下为什么在游戏结束前删除了这个用户吗?@assylias我可以在问题中更新代码吗?它会影响性能吗?什么不是最简单的解决方案?@Zuckermori请在中查看我的更新question@EddieBui是的,快速浏览看起来不错。谢谢,我更新了我的问题。但是,如果贴图是另一个对象的私有属性,如何同步
players
map?我可以同步
GameController.getPlayer()
?我不想让那些
players
mappublic@EddieBui您可以在任何对象上同步-例如,您可以创建一个
final object lock=new object()某个地方并使用它。@EddieBui不,你不想公开地图。从设计角度来看,将同步块放在同一个类上可能是有意义的,
players
是该类的一个属性。如果您不可能这样做,请按照@assylas的建议创建一个专用的锁对象。
//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());
            }
        }
    }
}