5

while(true) ループの奇妙な動作があります。コードは次のとおりです。

クラスのメンバーとして私は持っています:

static Queue<Object> response = new LinkedList<Object>();

... そして関数:

private void read() {

    while (true)
    {
        System.out.println("foo");
        if(!(response.isEmpty()))
        {

            if((Boolean)response.peek() == true)
            {
                view.dispose();
                LogInControler controler= new LogInControler();
                disableMasterLogin();
                response.poll();
                return;
            }
            else if((Boolean)response.poll() == false)
            {
                JOptionPane.showMessageDialog(view.getRootPane(), 
                        "Wrong username or password.");
                view.tfUsername.requestFocus();
                return;
            }
        }
    }
}

オブジェクトがサーバーから (Socket 経由で) 受信されると、InputController クラスはそのオブジェクトを適切なコントローラー (この場合は MasterLogInController) に渡し、Queue 応答に入れます。while(true) ループでその応答を待っていますが、「System.out.printline("foo");」を削除すると問題が発生します。ループは一度だけ入ります!? この syso 行を使用して、応答が受信されるまでループを実行するように while ループを「強制」します。ここで何が問題なのですか?

4

2 に答える 2

4

複数のスレッドが実行されていると仮定します。

System.out.printlnメモリ バリアを作成します。これにより、コードが他の方法では表示されない変数を認識できる可能性があります (同期がないため)。

特に、キューはスレッドセーフではなく、安全に公開されているようです。したがって、次のことが非常に考えられます。

  • whileループがresponsenull ==> NullPointerException として表示される場合があります
  • reponse.isEmpty()false を返すresponse.peek()可能性がありますが、null を返す可能性があります。これを a にキャストしBoolean、条件でボックス化解除しますif((Boolean)xxx == true)==> NullPointerException

原因を理解するのに役立つコメントで与えられた健全なアドバイスとは別に、コードをスレッドセーフにする必要があります。たとえば、スレッド セーフな BlockingQueueを使用できます。しかし、それはおそらく十分ではありません (さまざまな if / if / else if ステートメントがレイアウトされている方法と、それらのステートメントのそれぞれの間で別のスレッドによってキューが変更される可能性があるという事実のため)。

于 2012-12-09T23:26:59.610 に答える
2

何が起こっているのかは、JIT コンパイラーによってループが最適化されて存在しないようになっているためだと思います。がループ内で最初に呼び出されたときに true であり、それがブロックまたはメソッド内にない、またはマークされresponse.isEmpty()ていることに気付いた場合、JIT コンパイラーはそれが変更されないと判断し、空のように見えるものを削除する可能性があります。実行中のコードによるビジー ループ。responsesynchronizedvolatile

ステートメントを追加すると、println()少なくともループに目的が与えられます (JIT コンパイラーの目には)。そのため、その場合はループを実行したままにします。

これを修正するには、assylias からの顕著なアドバイスに加えて、次のようにすべての参照をブロックresponse内に配置できます。synchronized

public void read() {
    Boolean result = null;
    synchronized (response) {
        while (true) {
            result = (Boolean) response.poll();
            if (result != null) break;
            try {
                response.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
                // You could put return; here
            }
        }
    }
    // result should always be non null here
    if (result) {
         view.dispose();
         LogInControler controler = new LogInControler();
         disableMasterLogin();
    } else {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                JOptionPane.showMessageDialog(view.getRootPane(), "Wrong username or password");
                view.tfUsername.requestFocus();
            }
        });
    }
}

他のスレッドが応答をキューに追加している場合は、それも同期ブロックと呼び出しにあることを確認してnotifyAll()ください。

public void addResult(Object result) {
    synchronized (response) {
        response.add(result);
        response.notifyAll();
    }       
}
于 2012-12-10T00:26:32.220 に答える