跨线程和逻辑的同步

时间:2017-07-31 17:28:11

标签: 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维护在线玩家列表。

  • 当播放器断开连接时,我将该播放器的“已断开”标志设置为true(isDisconnected是Player类的布尔属性),而不是从播放器hashmap中删除播放器对象。

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

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

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

  • (2):从玩家hashmap获取现有的玩家对象。

  • (1):播放器断开连接标志仍为真,从播放器hashmap中删除播放器对象。

  • (2):播放器断开标志设置为false;播放器对象仅从(2)的线程内存中引用。当(2)结束时,参考文件消失,玩家对象被GC处理清除。

  • 因此,播放器已连接,但播放器数据未存储在程序存储器中。

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

先谢谢你了!

更新1 我可以像这样添加同步块:

//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 我使用分离的线程来处理连接和断开事件。如果玩家在当前游戏结束的同时重新连接,我的问题就会发生。

当玩家不在游戏中时(例如在大厅,私人房间......),玩家可以断开连接。在那些情况下,因为玩家没有做“渐进式”和“重要”动作,所以我不需要实现重新连接功能。

1 个答案:

答案 0 :(得分:1)

(1)和(2)需要在core.es5.js:1020 ERROR Error: Uncaught (in promise): TypeError: YT.Player is not a constructor TypeError: YT.Player is not a constructor at new VideoComponent (video.component.ts:27) at createClass (core.es5.js:10910) at createDirectiveInstance (core.es5.js:10744) at createViewNodes (core.es5.js:12180) at callViewAction (core.es5.js:12626) at execComponentViewsAction (core.es5.js:12535) at createViewNodes (core.es5.js:12207) at createRootView (core.es5.js:12075) at callWithDebugContext (core.es5.js:13458) at Object.debugCreateRootView [as createRootView] (core.es5.js:12775) at new VideoComponent (video.component.ts:27) at createClass (core.es5.js:10910) at createDirectiveInstance (core.es5.js:10744) at createViewNodes (core.es5.js:12180) at callViewAction (core.es5.js:12626) at execComponentViewsAction (core.es5.js:12535) at createViewNodes (core.es5.js:12207) at createRootView (core.es5.js:12075) at callWithDebugContext (core.es5.js:13458) at Object.debugCreateRootView [as createRootView] (core.es5.js:12775) at resolvePromise (zone.js:783) at resolvePromise (zone.js:754) at zone.js:831 at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:424) at Object.onInvokeTask (core.es5.js:3881) at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:423) at Zone.webpackJsonp.../../../../zone.js/dist/zone.js.Zone.runTask (zone.js:191) at drainMicroTaskQueue (zone.js:595) at <anonymous> 地图上进行同步,以便在获得锁定时,他们中的任何一个都能看到玩家地图和玩家的一致视图。的状态。