1

这是一个客户端-服务器程序。对于每个客户端,服务器都有一个方法,该方法检查是否有一些消息发送到该客户端。

代码:

        while (bool) {
            for(int j = 0;j<Start.bases.size();j++){
                if(Start.bases.get(j).getId() == id){
                    if(!Start.bases.get(j).ifEmpty()){
                        String output = Start.bases.get(j).getMessage();
                        os.println(output);
                        System.out.println(output +" *FOT* "+ addr.getHostName());
                    }
                }

            }

每个线程都有一个 id。所以一切似乎都很好,但我在这一行得到了奇怪的空指针异常

if(Start.bases.get(j).getId() == id){

id - 整数。这真的很奇怪,因为我已经在调试这部分运行并检查了“bases”和“id”不为空,并且 bases 有适当的字段。基地不为空。

顺便说一句,bases 是静态的(因为每个线程都可以使用它),并且在使用此方法之前声明了 bases。

这条线不会引起问题

            for(int j = 0;j<Start.bases.size();j++){

可能是因为方法 getId() 吗?

public int getId(){
    return id;

}

问题是什么?

已编辑。

  static ArrayList<Base> bases;
  bases = new ArrayList<Base>();

类基:

 public class Base {
private ServerThread st;
private int id;
private String name;
private ArrayList<String> messages;

public Base(String n, ServerThread s_t, int i_d){
    messages = new ArrayList<String>();
    st = s_t;
    name = n;
    id = i_d;
}

public String getName(){
    return name;
}
public int getId(){
    return id;
}
public ServerThread getThr(){
    return st;
}
public String getMessage(){
    String ret = "";

    if(!messages.isEmpty()){
        ret = messages.get(0);
        messages.remove(messages.get(0));
    }

    return ret;
}

public void addMessage(String m){
    messages.add(m);
}

public boolean ifEmpty(){
    return messages.isEmpty();
}
  }

谢谢。

4

2 回答 2

2

在这行代码中: (Start.bases.get(j).getId() == id

在这种情况下,您可能会有这样的例外:

1) bases is null - you said its wrong
2) bases.get(j) - it may occur only if you collection size was reduced during iteration(as mentioned Gray)
3) Start.bases.get(j).getId() is null. But as you mentioned getId() method return primitive int, so its not the case as in this situation you receive null ponter while casting - in line "    return id;".

所以你应该检查第二种情况。

于 2012-05-21T20:42:36.427 回答
1

鉴于这种:

“我已经在调试这部分运行并检查了“bases”和“id”是否不为空,并且 bases 有适当的字段”

还有这个:

bases 是静态的(因为每个线程都可以使用它)

我认为你很可能有比赛条件。在竞争条件下,有两个线程同时访问同一个数据结构(在本例中为 Start.bases)。大多数情况下,一个线程的代码完成得更快,一切都按照您期望的方式进行,但有时另一个线程会抢占先机或比平时快一点,事情就会“繁荣”。

当您引入带有断点的调试器时,您几乎可以保证带有断点的代码将最后执行,因为您已经在所有其他线程仍在运行时停止了它。

我建议您的列表的大小可能会在您执行时发生变化。当用户离开时,他们的条目是否从“基本”列表中删除?是否有其他情况可以在执行期间从另一个线程更改列表?

我建议的第一件事是您将代码切换为使用迭代器而不是直接的“for”循环。它不会让问题消失(它实际上可能使它更明显),但它会使正在发生的事情更加清晰。您将在修改发生时获得 ConcurrentModificationException ,而不是仅在发生某些更改组合时才有帮助的 NullPointerException 。):

        for(Base currentBase : Start.bases)
        {
            if(currentBase.getId() == id && !currentBase.ifEmpty())
            {
                String output = currentBase.getMessage();
                os.println(output);
                System.out.println(output +" *FOT* "+ addr.getHostName());
            }
        }

如果您确实在上面的代码中遇到了并发修改异常,那么您肯定是在处理竞争条件。这意味着您必须同步您的代码。

有几种方法可以做到这一点,具体取决于您的应用程序的结构。

假设竞争只发生在这段代码和另一段代码之间(执行从列表中删除的部分),您可以通过将两个代码块都包装在

synchronized(Start.bases)
{
   [your for-loop/item removal code goes here]
}

这将在列表本身上获得一个锁,这样这两段代码就不会尝试在不同的线程中同时更新同一个列表。(请注意,它不会停止对 Base 对象本身的并发修改,但我怀疑这就是这种情况下的问题)。

综上所述,任何时候你有一个变量可以被多个线程读/写访问,它真的应该被同步。这是一项相当复杂的工作。如果可以的话,最好将同步保持在您管理的对象内。这样您就可以在一个地方查看所有同步代码,从而减少意外造成死锁的可能性。(在上面的代码中,您需要在 Start 类中创建“for”循环以及使用该列表的任何其他方法,然后将“bases”设为私有,以便应用程序的其余部分必须使用这些方法)。

如果没有看到代码中访问此列表的所有其他位置,我无法确切说明您应该进行哪些更改,但希望这足以让您入门。请记住,Java 中的多线程需要非常精巧的手!

于 2012-05-22T00:32:35.857 回答