简体   繁体   English

Java如何正确同步多个线程上的arraylist?

[英]Java how do I properly synchronize an arraylist over multiple threads?

I have a server that creates a thread for each client that connects to it. 我有一台服务器,它为连接到它的每个客户端创建一个线程。 The main server class is called "Server" while each client is managed by a thread of a class "ClientManager" originating from Server. 主服务器类称为“服务器”,而每个客户端由源自服务器的类“ ClientManager”的线程管理。 Server has a static arraylist of tiles (for a mahjong game) and when each client draws, the ClientManager removes that many tiles from the arraylist. 服务器有一个静态的图块阵列列表(用于麻将游戏),当每个客户端绘制时,ClientManager将从阵列列表中删除那么多的图块。 I have the method that does this Synchronized (probably incorrectly), but when I run the program, it is as if the first player does not properly remove tiles from the list. 我有执行此同步的方法(可能不正确),但是当我运行该程序时,好像第一个播放器没有从列表中正确删除磁贴。 When I look at my debug list it says "Player 1 draws, 144 tiles remain" when it should say 131. If I run the program in debug mode, it works perfectly fine. 当我查看调试列表时,它说“ Player 1绘制,剩余144个图块”,应显示131。如果我在调试模式下运行该程序,则效果很好。 If I add Thread.sleep to the main method in Server that handles all of this, it works, but I don't like the incredibly long wait and would like to have the arraylists just synchronize properly and update properly. 如果我将Thread.sleep添加到处理所有这些问题的Server的main方法中,则可以正常工作,但是我不喜欢令人难以置信的漫长等待,而是希望让arraylists正确同步并正确更新。 The first run should make the list go down by 13, then the next character draws 13 and so on. 第一次运行会使列表下降13,然后下一个字符绘制13,依此类推。 After that they would each draw 1, but the removing is not being reflected in the game. 此后,他们将各取1张,但删除没有反映在游戏中。

Here is the relevant code from the client, server and client manager 这是来自客户端,服务器和客户端管理器的相关代码

Main: 主要:

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;
            }
        }

    }

Server: 服务器:

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();


    }
}

ClientManager: ClientManager:

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;
    }

}

Create an interface whose implementation enforces a single instance of your list (singleton). 创建一个接口,该接口的实现会强制执行列表的单个实例(单例)。

Example interface 介面范例

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

In it's implementation enforce a single instance 在其实施中强制执行一个实例

   public List<Item> getInstance(){

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

      return myList;

    }

This way you can be sure each client is using the same list, this will eliminate each client having their own copy of a synchronized list. 这样,您可以确保每个客户端都使用相同的列表,这将消除每个客户端拥有自己的同步列表副本。

Better Description 更好的描述

Or if you just want to make a class 或者如果您只是想上课

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

    public List<Item> getInstance(){

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

        return myList;

    }
}

You can simplify this as follows: 您可以如下简化:

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);
}

You preallocate all of the players' hands in a single thread, so you don't need synchronization. 您可以在一个线程中预分配所有玩家的手,因此不需要同步。 You then use a thread-safe ConcurrentHashMap to store the players' hands so that again you don't need synchronization (the map takes care of that). 然后,您可以使用线程安全的ConcurrentHashMap来存储玩家的手,这样您就无需再次进行同步了(地图可以解决此问题)。

An alternative is to replace the ArrayList<tile> with a ConcurrentLinkedQueue<Tile> , where again you don't need to worry about synchronization because the data structure is handling it for you. 另一种选择是用ConcurrentLinkedQueue<Tile>替换ArrayList<tile> ,在这里您无需担心同步,因为数据结构正在为您处理。 Note however that this implementation would be "cheating" in that Player1 might not get the first 13 tiles, Player2 might not get the second 13 tiles, and so on - the tile allocation would be non-deterministic. 但是请注意,此实现将“作弊”,因为Player1可能不会获得前13个图块,Player2可能不会获得后13个图块,依此类推-瓦片分配将是不确定的。 However, I'm assuming that you're shuffling the tiles, in which case it shouldn't make any difference what order the players draw in. If this would be unacceptable then I'd recommend going with the preallocated ConcurrentHashMap (you could also accomplish this with the ConcurrentLinkedQueue , but it would be just as complicated as using a global ArrayList ) 但是,我假设您要对图块进行改组,在这种情况下,应该不影响玩家绘制的顺序。如果这不可接受,那么我建议您使用预先分配的ConcurrentHashMap (您也可以使用ConcurrentLinkedQueue完成此操作,但这与使用全局ArrayList一样复杂)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM