【问题标题】:Java how do I properly synchronize an arraylist over multiple threads?Java如何在多个线程上正确同步arraylist?
【发布时间】:2015-11-03 03:13:58
【问题描述】:

我有一个服务器,它为连接到它的每个客户端创建一个线程。主服务器类称为“Server”,而每个客户端由源自 Server 的“ClientManager”类的线程管理。服务器有一个静态的棋子数组列表(用于麻将游戏),当每个客户端绘制时,ClientManager 会从数组列表中删除那么多的棋子。我有执行此同步的方法(可能不正确),但是当我运行程序时,就好像第一个玩家没有正确地从列表中删除图块。当我查看调试列表时,它显示“玩家 1 绘制,剩余 144 个图块”,而它应该显示为 131。如果我在调试模式下运行程序,它工作得非常好。如果我将 Thread.sleep 添加到处理所有这些的 Server 中的 main 方法中,它可以工作,但我不喜欢令人难以置信的长时间等待,并且希望数组列表正确同步并正确更新。第一次运行应该使列表下降 13,然后下一个字符绘制 13,依此类推。之后,他们将各抽 1 分,但移除并没有反映在游戏中。

这是来自客户端、服务器和客户端管理器的相关代码

主要:

public class Main {
        static GamePanel gameGUI = new GamePanel();
        static Client client;
        static Player me = new Player();
        //!!? is used to mark a username command

    @SuppressWarnings("unchecked")
    public static void main(String[] args) throws IOException{
        String host = "107.199.245.55";
        //String host = "107.199.244.144";
        //String host = "localhost";
        boolean created = false;
        ArrayList<?> Temp = new ArrayList<Object>();
        ArrayList<Tile> TileStack = new ArrayList<Tile>();
        Object object = null;
        int tiles = 0;
        String username = "!!?" + JOptionPane.showInputDialog("Username:");
        if(username.substring(3, username.length()).equals("null")){return;}
        while(username.substring(3, username.length()).isEmpty()){
        username = "!!?" + JOptionPane.showInputDialog("Must have an actual name\nUsername:");
        }
        int port = 27016; 
        //int port = 2626;
        //int port = 4444;


        client = new Client(host, port, username);
        client.send(username);
        gameGUI.initialize();
        gameGUI.lblPlayerNameYou.setText(username.substring(3, username.length()));
        waitForPlayers();

        while(true)
        {
            try {
                object = client.receive();
                if(object instanceof ArrayList<?>)
                {
                    Temp = (ArrayList<?>) object;
                    if(Temp.get(0) instanceof String)
                    {
                        setUpNames(username, Temp);
                    }
                    else if(Temp.get(0) instanceof Tile)
                    {
                        if(!created){
                        TileStack = (ArrayList<Tile>) Temp;
                        created = true;
                        for (int i = 0; i < 13; i++){me.drawOneTile(TileStack);}
                        client.send(13);
                        }
                        else if(created){
                        me.drawOneTile(TileStack);
                        client.send(1);
                        }
                        gameGUI.displayHand(me.hand);
                    }
                }
                else if(object instanceof Integer){
                    tiles = (int) object;
                    gameGUI.tilesRemaining.setText("Remaining Tiles: " + tiles);
                }
            } catch (IOException e) {
                e.printStackTrace();
                JOptionPane.showMessageDialog(null, "You were disconnected. Exiting game.");
                gameGUI.dispose();
                break;
            }
        }

    }

服务器:

public class Server {

    public static ArrayList<ObjectOutputStream> ConnectionArray = new ArrayList<ObjectOutputStream>();
    public  ArrayList<String> CurrentUsers = new ArrayList<String>();
    public static ArrayList<Socket> ConnectionArraySOCKET = new ArrayList<Socket>();
    public  ArrayList<Tile> TileStack = new ArrayList<Tile>();
    public static ServerGUI serverGUI;
    public int port;
    public ServerSocket SERVERSOCK;
    public static GameLoop game = new GameLoop();
    public static Server server;

    public Server(int port){
        this.port = port;
        try {
        SERVERSOCK = new ServerSocket(port);
        } catch (IOException e) {
            System.out.println("Unable to start");
            e.printStackTrace();
        }
        serverGUI = new ServerGUI();
        serverGUI.initialize();

    }
    public ArrayList<String> getCurrentUsers(){return CurrentUsers;}
    public ArrayList<Tile> getTileStack(){return TileStack;}
    public void waitForClients(ServerSocket SERVERSOCK)
    {
        serverGUI.addText(serverGUI.getDebugPane(), "Waiting for clients...");

        while(ConnectionArray.size() < 4 && CurrentUsers.size() < 4)
        {
            try{
                if (!(ConnectionArray.size() == 0)) shareToAll(ConnectionArray.size());
                Socket client = SERVERSOCK.accept();
                ConnectionArray.add(new ObjectOutputStream(client.getOutputStream()));
                ConnectionArraySOCKET.add(client);
                Thread t = new Thread(new ClientManager(client));
                t.start();
                if (!(ConnectionArray.size() == 0)) shareToAll(ConnectionArray.size());
            }
            catch(IOException e){e.printStackTrace();}
        }
    }

    public static void shareToAll(Object o){
        for(ObjectOutputStream stream : ConnectionArray)
        {
            try{
            Thread.sleep(100);
            stream.writeObject(o);
            stream.reset();
            stream.flush();
            }
            catch(Exception e){
                e.printStackTrace();
            }
        }
    }

    public static void distributeTileStack(Object o, int playerNum){
        try {
            ConnectionArray.get(playerNum).writeObject(o);
            ConnectionArray.get(playerNum).reset();
            ConnectionArray.get(playerNum).flush();
            Thread.sleep(100);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public static boolean checkConnection()
    {
        if(ConnectionArray.size() < 4) return false;
        else return true;
    }



    public static void main(String[] args) throws InterruptedException
    {   
        server = new Server(Integer.parseInt(JOptionPane.showInputDialog("Port:")));
        ArrayList<Tile> temp = new ArrayList<Tile>();
        while(!serverGUI.started){
            System.out.println("LOOP");
        }

        server.waitForClients(server.SERVERSOCK);
        Server.shareToAll(server.getCurrentUsers());

        game.createStack();
        server.TileStack = game.getStack();
        for (int i = 0; i <= 3; i++){
            serverGUI.addText(serverGUI.getDebugPane(), "Player " + (i + 1) + " drawing tiles.");
            temp = server.getTileStack();
            Server.distributeTileStack(server.TileStack, i);            
            serverGUI.addText(serverGUI.getDebugPane(), "Tilestack size: " + server.getTileStack().size());
            Server.shareToAll(server.getTileStack().size());
        }

        while(checkConnection()){
            // game logic here


        }

        JOptionPane.showMessageDialog(null, "Player disconnected. The server will now close.");
        serverGUI.btnStopServer.doClick();
        serverGUI.dispose();


    }
}

客户经理:

public class ClientManager extends Thread implements Runnable {

    Socket SOCK;
    String username;
    public ClientManager(Socket SOCK)
    {
        this.SOCK = SOCK;
    }

    public void run(){
        boolean working = true;
        try{
            ObjectInputStream inStream = new ObjectInputStream(SOCK.getInputStream());
            while(working){
                working = handle(inStream);
            }
        }
        catch(SocketException e){
            e.printStackTrace();
            System.out.println("Cannot get inputstream");
        }
        catch(IOException e){
            e.printStackTrace();
        }
    }
    public boolean handle(ObjectInputStream inStream){
        Object object = null;
        String string;
        try{
            object = inStream.readObject();
            if(object instanceof String)
            {
                string = (String)object;
                if(string.startsWith("!!?")){
                username = string.substring(3, string.length());
                synchronized (Server.class){
                Server.server.CurrentUsers.add(username);
                }
                Server.serverGUI.addText(Server.serverGUI.getDebugPane(), "User connected: " + username + SOCK.getRemoteSocketAddress());
                Server.serverGUI.addText(Server.serverGUI.getUsersPane(), username);
                Server.serverGUI.addText(Server.serverGUI.getDebugPane(), "Number of users: " + Server.server.CurrentUsers.size());
                }

            }
            if (object instanceof Integer)
            {
                synchronized(Server.class){
                    for (int i = 0; i < (int) object; i++)
                        Server.server.TileStack.remove(0);
                }
            }
        }
        catch(ClassNotFoundException ce){ce.printStackTrace();}
        catch(IOException e){
            e.printStackTrace();
            for(int i = Server.ConnectionArray.size() - 1; i >= 0; i--)
            {
                if(Server.ConnectionArraySOCKET.get(i).equals(SOCK))
                {
                    Server.serverGUI.addText(Server.serverGUI.getDebugPane(), "User " + Server.server.CurrentUsers.get(i) + SOCK.getRemoteSocketAddress() + " disconnected");
                    Server.serverGUI.clearText(Server.serverGUI.getUsersPane());
                    Server.server.CurrentUsers.remove(i);
                    Server.serverGUI.addText(Server.serverGUI.getDebugPane(), "Number of users: " + Server.server.CurrentUsers.size());
                    Server.ConnectionArraySOCKET.remove(i);
                    Server.ConnectionArray.remove(i);
                    for (int i2 = 1; i2 <= Server.server.CurrentUsers.size(); i2++){
                        Server.serverGUI.addText(Server.serverGUI.getUsersPane(), Server.server.CurrentUsers.get(i2-1));
                    }
                    if (!(Server.ConnectionArray.size() == 0)) Server.shareToAll(Server.ConnectionArray.size());
                }


            }
            return false;

        }
        return true;
    }

}

【问题讨论】:

  • 您是否考虑过使用 Collections.synchronizedList 将原始 List 包装在同步包装器中
  • 我试过一次,它似乎没有解决任何问题。我在我的 Server 类中创建了一个 Public Static List CurrentUsers = Collections.synchronizedList(new ArrayList) ,所有其他类都访问了 CurrentUsers 但它没有改变任何东西。
  • 我去看看。谢谢。

标签: java multithreading networking synchronization server


【解决方案1】:

创建一个接口,其实现强制执行列表的单个实例(单例)。

示例界面

interface SharedResource<T>{ 
   public void add(T);
   public T remove();
}

在它的实现中强制执行单个实例

   public List<Item> getInstance(){

        if(myList == null){
             myList = Collections.synchronizedList(TileStack);
        }

      return myList;

    }

这样您可以确保每个客户端都使用相同的列表,这将消除每个客户端都拥有自己的同步列表副本。

Better Description

或者如果你只是想上课

public class MyResource{
    List<Item> myList;
    private MyResource(){}

    public List<Item> getInstance(){

        if(myList == null){
             myList = Collections.synchronizedList(new ArrayList<Item>());
        }

        return myList;

    }
}

【讨论】:

  • 我不经常使用接口。所以我的接口只有Public List&lt;T&gt; getInstance(),然后在我的clientManager类中我会实现这个接口并写下你在第二个代码块中写的内容?
  • @snowllama 你可以这样做。如果您不想使用接口,则可以创建一个具有相同 getInstance() 方法和标记为私有的构造函数的类。因此,如果您不想使用界面,请不要被界面抛弃。
  • 您可以只创建一个包含同步列表的类和 public List getInstance() 等...
【解决方案2】:

您可以将其简化如下:

private ArrayDeque<Tile> tiles;
private ConcurrentHashMap<Integer, ArrayList<Tile>> preallocate;

private void preAllocate() {
    for(int i = 0; i < 4; i++) {
        ArrayList<Tile> temp = new ArrayList<>();
        for(int j = 0; j < 13; j++) {
            temp.add(tiles.pop());
        }
        preallocate.put(i, temp);
    }
}

public ArrayList<Tile> get(int playerId) {
    return preallocate.get(playerId);
}

你在一个线程中预先分配了所有玩家的手,所以你不需要同步。然后你使用线程安全的ConcurrentHashMap 来存储玩家的手,这样你就不需要同步了(地图会处理这个)。

另一种方法是将ArrayList&lt;tile&gt; 替换为ConcurrentLinkedQueue&lt;Tile&gt;,您无需担心同步问题,因为数据结构会为您处理。但是请注意,此实现将是“作弊”,因为 Player1 可能无法获得前 13 个图块,Player2 可能无法获得第二个 13 个图块,依此类推——图块分配将是不确定的。但是,我假设您正在洗牌,在这种情况下,玩家绘制的顺序不应该有任何区别。如果这是不可接受的,那么我建议使用预先分配的 ConcurrentHashMap(您也可以使用ConcurrentLinkedQueue 完成此操作,但它与使用全局ArrayList 一样复杂)

【讨论】:

  • 太棒了!谢谢你,我会试试这个。
  • @snowllama 这假设玩家无法通过伪造他们的 playerId 并获得其他玩家的手来作弊。如果这个假设是错误的,那么对 playerIds 使用随机的UUID
猜你喜欢
  • 2016-08-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-28
  • 2023-03-17
  • 2020-05-18
  • 2014-07-27
  • 2016-10-31
相关资源
最近更新 更多