这是一个客户端-服务器程序。
每个客户端服务器都有一个方法,该方法检查是否有一些消息发送给该客户端。
码:
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();
}
}
谢谢。
最佳答案
鉴于这种:
“我已经在调试此部分中运行,并检查了“ 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中的多线程需要非常精巧的手!