【问题标题】:Accessing array lists from threads从线程访问数组列表
【发布时间】:2017-03-04 20:01:14
【问题描述】:

我正在为我的网络课程制作多人冒险游戏。我有一个客户端和一个服务器,服务器是多线程的,每当连接新客户端时就会启动一个新线程。我有一个跟踪玩家的数组列表,以确保不添加新玩家。出于某种原因,当新客户端连接时,它会取代旧客户端并填充新位置。这是我这部分的代码

public class ClientHandler implements Runnable{
private AsynchronousSocketChannel clientChannel;
private static String command[];
private static String name;
private static GameCharacter character;
public ClientHandler(AsynchronousSocketChannel clientChannel)
{
    this.clientChannel = clientChannel;
}

public void run(){
    try{
        System.out.println("Client Handler started for " + this.clientChannel);
        System.out.println("Messages from Client: ");
        while ((clientChannel != null) && clientChannel.isOpen()) {
            ByteBuffer buffer = ByteBuffer.allocate(32);
            Future result = clientChannel.read(buffer);
            //Wait until buffer is ready
            result.get();
            buffer.flip();
            String message = new String(buffer.array()).trim();
            if(message == null || message.equals(""))
            {
                break;
            }
            System.out.println(message);
            clientChannel.write(buffer);
            try {
                //Add the character to the routing table and the character table
                if (message.contains("connect")) {
                    System.out.println("I'm here too?");
                    command = message.split(" ");
                    name = command[1];
                    AdventureServer.userInfo.put(name, this);
                    //Check to see if this game character exists
                    GameCharacter test;
                    boolean exists = false;
                    for(int i=0; i < AdventureServer.characters.size(); i++)
                    {
                        test = AdventureServer.characters.get(i);
                        System.out.println(test.getName());
                        System.out.println(this.name);
                        if(this.name.equals(test.getName()))
                        {
                            System.out.println("already Here");
                            exists = true;
                        }
                    }
                    if (exists == true)
                    {
                        //This person has connected to the server before
                    }
                    else {
                        //Create a game character
                        System.out.println("didn't exist before");
                        character = new GameCharacter(this.name, World.getRow(), World.getCol());
                        AdventureServer.characters.add(AdventureServer.userInfo.size() - 1, character);
                        System.out.println(AdventureServer.characters.get(0).getName() + " " +AdventureServer.characters.get(1).getName());
                    }
                }

我了解底部的打印行会为第一个连接的客户端引发错误,但这不是问题的一部分。 这是服务器的声明

public class AdventureServer {
public static Map<String, ClientHandler> userInfo = new HashMap<>();
public static World world;
public static List<GameCharacter> characters = Collections.synchronizedList(new ArrayList<>());
public static void main(String args[]) {
    //Create the games map that all of the users will exist on
    world = new World(args[0]);

    System.out.println("Asynchronous Chat Server Started");
    try {
        AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open();
        InetSocketAddress hostAddress = new InetSocketAddress("192.168.1.7", 5000);
        serverChannel.bind(hostAddress);
        while (true)
        {
            System.out.println("Waiting for client to connect");
            Future acceptResult = serverChannel.accept();
            AsynchronousSocketChannel clientChannel = (AsynchronousSocketChannel) acceptResult.get();
            new Thread (new ClientHandler(clientChannel)).start();
        }
    } catch (Exception e) {
        System.out.println("error interrupted");
        e.printStackTrace();
        System.exit(0);
    }
}
}

这是我的游戏角色构造函数

public class GameCharacter {
public static int xpos;
public static int ypos;
private static String name;
private static int rowSize;
private static int columnSize;
static List<String> inventory = new ArrayList<>();

//Constructor
GameCharacter(String n, int rSize, int cSize)
{
    xpos = 0;
    ypos = 0;
    name = n;
    rowSize = rSize;
    columnSize = cSize;
}

GameCharacter()
{
    xpos = 0;
    ypos = 0;
    name = "billybob";
    rowSize = 10;
    columnSize = 10;
}

【问题讨论】:

  • 就是这行代码搞砸了 dventureServer.characters.add(character);
  • 你确定这不仅仅是因为ClientHandler 中的字段是静态的吗?这意味着每个客户端处理程序共享相同的字符、相同的名称和相同的命令。

标签: java multithreading arraylist static network-programming


【解决方案1】:

你可以试试:

public static volatile List<GameCharacter> characters = Collections.synchronizedList(new ArrayList<>());

更新: 问题是您使用的是非同步的 HashMap userInfo。 从以下位置更改该行:

AdventureServer.characters.add(AdventureServer.userInfo.size() - 1, character);

收件人:

AdventureServer.characters.add(character);

或者让你的 HashMap 同步:

public static Map<String, ClientHandler> userInfo = Collections.synchronizedMap(new HashMap<>());

所有这些静态声明都有问题,您应该删除它们。一般来说,您应该避免使用静态。

客户端处理程序:

private static String command[];
private static String name;
private static GameCharacter character;

游戏角色:

public static int xpos;
public static int ypos;
private static String name;
private static int rowSize;
private static int columnSize;
static List<String> inventory = new ArrayList<>();

顺便说一句,这样你的 Class 更像 Java 代码应该是这样的:

import java.util.ArrayList;
import java.util.List;

public class GameCharacter {
private int xpos;
private int ypos;
private String name;
private int rowSize;
private int columnSize;

private List<String> inventory = new ArrayList<>();

// Constructor
GameCharacter(String n, int rSize, int cSize) {
    this.xpos = 0;
    this.ypos = 0;
    this.name = n;
    this.rowSize = rSize;
    this.columnSize = cSize;
}

GameCharacter() {
    this.xpos = 0;
    this.ypos = 0;
    this.name = "billybob";
    this.rowSize = 10;
    this.columnSize = 10;
}

public int getXpos() {
    return xpos;
}

public void setXpos(int xpos) {
    this.xpos = xpos;
}

public int getYpos() {
    return ypos;
}

public void setYpos(int ypos) {
    this.ypos = ypos;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public int getRowSize() {
    return rowSize;
}

public void setRowSize(int rowSize) {
    this.rowSize = rowSize;
}

public int getColumnSize() {
    return columnSize;
}

public void setColumnSize(int columnSize) {
    this.columnSize = columnSize;
}

public List<String> getInventory() {
    return inventory;
}

public void setInventory(List<String> inventory) {
    this.inventory = inventory;
}

}

【讨论】:

  • 不幸的是没有做到这一点
  • 抱歉,还是坏了。在我调用名称更改的构造函数之后。所以在我将它添加到数组列表之前
  • 问题出在构造函数上,我只是加了给大家看看。
  • 你是对的,静态字段是问题,谢谢你的帮助
【解决方案2】:

考虑到可读性、可测试性和风格,我还建议您不要直接访问属于另一个类的数据结构。而不是

   Adventureserver.characters.add(blah blah)

我建议将字符设置为 Adventureserver 的私有字段,然后创建一个方法来添加或删除字符。事实上,我倾向于不让角色静态化——这并没有真正的优势,而且您可能在某些时候想要运行多个 Adventureserver。

有点像这样:

public class AdventureServer {
<...>
private List<GameCharacter> characters = Collections.synchronizedList(new ArrayList<>);

<...>
public void addCharacter(GameCharacter char) {
   <... error checking ...>
   characters.add(char);
}
public void removeCharacter(GameCharacter char) {
   <... implementation ... >
}
public boolean isCharacterHere(GameCharacter char) {
}
public List<GameCharacter> getCharacters() {
  <... you could either return characters here, or a copy of it,
   depending upon how paranoid you want to be >

【讨论】:

  • 问题出在构造函数上,我只是加了给大家看看。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多