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

时间:2015-08-11 03:07:40

标签: java multithreading networking synchronization server

我有一台服务器为每个连接它的客户端创建一个线程。主服务器类称为" Server"而每个客户端都由一个类的线程管理" ClientManager"源自Server。服务器有一个静态的磁贴列表(用于麻将游戏),当每个客户端绘制时,ClientManager从arraylist中删除了许多磁贴。我有同步(可能不正确)的方法,但是当我运行程序时,就好像第一个播放器没有正确地从列表中删除tile。当我查看我的调试列表时,它说"播放器1绘制,剩下144个磁贴"当它应该说131.如果我在调试模式下运行程序,它的工作完全正常。如果我将Thread.sleep添加到处理所有这些的服务器中的main方法,它可以工作,但我不喜欢漫长的等待,并希望让arraylists正确同步并正确更新。第一次运行应该使列表减少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();


    }
}

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

}

2 个答案:

答案 0 :(得分:0)

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

示例界面

   public List<Item> getInstance(){

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

      return myList;

    }

在其实施中强制执行单个实例

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

    public List<Item> getInstance(){

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

        return myList;

    }
}

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

Better Description

或者,如果你只是想上课

public void CustomNotification(String title_notify,String image_notify) {
    // Using RemoteViews to bind custom layouts into Notification
    RemoteViews remoteViews = new RemoteViews(getPackageName(),
            R.layout.customnotification);

    // Set Notification Title
    String strtitle = title_notify;
    // Set Notification Text
    String strtext = "";

    // Open NotificationView Class on Notification Click
    Intent intent = new Intent(this, Notification_ListView.class);
    // Send data to NotificationView Class
    intent.putExtra("title", strtitle);

    intent.putExtra("location", location);
    intent.putExtra("text", strtext);
    intent.putExtra("user_id", user_id);
    intent.putExtra("country_id", country_id);
    // Open NotificationView.java Activity
    PendingIntent pIntent = PendingIntent.getActivity(this, 0, intent,
            PendingIntent.FLAG_UPDATE_CURRENT);
    bitmap = getBitmapFromURL(image_notify);

    NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
            // Set Icon
            .setSmallIcon(getNotificationIcon())
            // Set Ticker Message  
            .setTicker("")
            // Dismiss Notification
            .setAutoCancel(true)
            // Set PendingIntent into Notification
            .setContentIntent(pIntent)
            // Set RemoteViews into Notification
            .setContent(remoteViews);

    // Locate and set the Image into customnotificationtext.xml ImageViews
    remoteViews.setImageViewBitmap(R.id.imagenotileft,bitmap);
    //remoteViews.setImageViewResource(R.id.imagenotiright,R.drawable.androidhappy);

    // Locate and set the Text into customnotificationtext.xml TextViews
    remoteViews.setTextViewText(R.id.title,strtitle);
    remoteViews.setTextViewText(R.id.text,strtext);

    // Create Notification Manager
    NotificationManager notificationmanager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    // Build Notification with Notification Manager
    notificationmanager.notify(0, builder.build());

}

答案 1 :(得分:0)

您可以按如下方式简化:

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<tile>替换为ConcurrentLinkedQueue<Tile>,您再次无需担心同步,因为数据结构正在为您处理。但请注意,此实现将“作弊”,因为Player1可能无法获得前13个区块,Player2可能无法获得第二个13区块,依此类推 - 区块分配将是不确定的。但是,我假设你正在洗牌,在这种情况下,它不会对玩家的顺序产生任何影响。如果这是不可接受的,那么我建议使用预先分配的ConcurrentHashMap (您也可以使用ConcurrentLinkedQueue完成此操作,但它会像使用全局ArrayList)一样复杂