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」がnullではなく、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;".

したがって、2番目のケースを確認する必要があります。

于 2012-05-21T20:42:36.427 に答える
1

これを考えると:

「この部分のデバッグを実行し、「bases」と「id」がnullではなく、basesに適切なフィールドがあることを確認しました。」

この:

ベースは静的です(すべてのスレッドがそれを使用できるため)

競合状態になっている可能性が高いと思います。競合状態では、2つのスレッドが同時に同じデータ構造(この場合はStart.bases)にアクセスします。ほとんどの場合、一方のスレッドのコードはより速く完了し、すべてが期待どおりに進みますが、もう一方のスレッドが有利なスタートを切るか、通常よりも少し速くなり、事態が「ブーム」になることがあります。

ブレークポイントのあるデバッガーを導入すると、他のすべてのスレッドがまだ実行されている間に実行の途中で停止したため、ブレークポイントのあるコードが最後に実行されることがほぼ保証されます。

実行すると、リストのサイズが変わる可能性があります。ユーザーが離れると、そのエントリは「ベース」リストから削除されますか?実行中にリストを別のスレッドから変更できる状況は他にありますか?

最初に提案するのは、まっすぐな「for」ループではなく、イテレータを使用するようにコードを切り替えることです。問題がなくなることはありませんが(実際には問題が目立つようになる可能性があります)、何が起こっているのかがより明確になります。変更の特定の組み合わせが発生した場合にのみ、あまり役に立たないNullPointerExceptionではなく、変更が発生した時点でConcurrentModificationExceptionが発生します。):

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

これにより、リスト自体のロックが取得されるため、これら2つのコードは、異なるスレッドで同じリストを同時に更新しようとはしません。(Baseオブジェクト自体への同時変更は停止しないことに注意してください。ただし、この場合はそれが問題になるとは思えません)。

とはいえ、複数のスレッドによって読み取り/書き込みアクセスされる変数がある場合は常に、実際に同期する必要があります。それはかなり複雑な仕事です。可能であれば、管理しているオブジェクト内で同期を維持することをお勧めします。そうすれば、すべての同期コードを1か所で確認できるため、誤ってデッドロックが発生する可能性が低くなります。(上記のコードでは、「for」ループをStartクラス内のメソッドと、そのリストを使用する他のメソッドにする必要があります。次に、「bases」をプライベートにして、アプリケーションの残りの部分がこれらのメソッドを使用するようにします。 )。

このリストにアクセスするコード内の他のすべての場所を確認しないと、どのような変更を加える必要があるかを正確に言うことはできませんが、うまくいけば、それで十分に始めることができます。Javaでのマルチスレッドには、非常に繊細な手が必要であることを忘れないでください。

于 2012-05-22T00:32:35.857 に答える