0

単一のメンバーメッセージ(文字列)を持つクラスBaseがあるとします。このBaseクラスを拡張する別のクラスBaseHandler。このハンドラーには、値をベースに設定して出力するprintメソッドがあります。print の呼び出しの最後に、メッセージを null に設定しています。50000 スレッドを作成してハンドラーの print メソッドを実行すると、ときどき null ポインター例外が発生します。

質問:
値を明示的に代入しているときに null ポインター例外がスローされるのはなぜですか?
この場合、各スレッドはどのようにベースをインスタンス化しますか?
ソリューションは Base.message を揮発性としてマークし、null 割り当てを削除しますか? (つまり、Base.messageでスレッドセーフを達成する方法

以下のコードを見てください。

public class Base {
     public String message;
 }

public class BaseHandler extends Base{

    protected static final Object lock = new Object();

    public void printMessage( ){

        synchronized ( lock ) { //This block is thread safe
            System.out.println( message.toUpperCase( ) );
            message = null;
        }
    }

}

public class Test {   
    public static void main(String[] args){       
        final BaseHandler handler = new BaseHandler();
        for (int i = 0; i < 50000; i++) {
            Runnable task = new Runnable(){
                @Override 
                public void run( ) {
                    handler.message = "Hello world! ";
                    handler.printMessage( );
                }                
            };
            Thread worker = new Thread(task);
            worker.setName(String.valueOf(i));
            worker.start();
        }
    }
}
4

6 に答える 6

4

値を明示的に代入しているときに null ポインター例外がスローされるのはなぜですか?

次の実行を想像してください。

  • スレッド 1:handler.message = "Hello world! ";
  • スレッド 2:handler.message = "Hello world! ";
  • Thread1: ロックを取得し、メッセージを出力して null に設定します
  • thread2: ロックを取得し、印刷を試みますがmessage.toUpperCase()、NPE をスローします。

問題は、以下の 2 行がアトミックでないことです。

handler.message = "Hello world! ";
handler.printMessage();

ソリューション

達成しようとしていることに応じて、いくつかの代替手段があります。

  • これらの2行をsynchronized(lock)ブロックに入れて、2つの呼び出しをアトミックにすることができます
  • パラメータを printMessage メソッドに渡すことができます: printMessage(message)、共有変数の問題を取り除きます
  • 呼び出しごとにクラスのインスタンスを作成して、共有変数の問題も取り除くことができます
  • ...
于 2012-07-30T16:43:41.340 に答える
2

これがあなたの問題です:

handler.message = "Hello world! ";
handler.printMessage( );

これらの2つの操作はアトミックではなく、アトミックprintMessage()です。だからこれは時々起こることです:

  1. スレッドAはmessageフィールドを変更します
  2. スレッドBが起動し、それも変更します
  3. スレッドBは動作を継続し、呼び出しますprintMessage()
  4. printMessage()messageスレッドBで、フィールドを終了してクリーンアップします
  5. スレッドAが復元され、を呼び出しますprintMessage()。災害が発生します

コードをスレッドセーフにする場合は、これら2つの操作をアトミックにする必要があります。コードには他にもいくつかの問題があるため、アドバイスするのは難しいです:パブリック可変フィールド、可視性の問題、ロックが不必要に静的です...

この擬似コードを変更できる場合は、 (妥当なように)messageの引数として渡すだけで、スレッドセーフとマルチスレッドについては忘れてしまいます。printMessage()コードは安全です。

于 2012-07-30T16:45:31.967 に答える
1

脆弱な拡張機能と共有された可変状態を使用して、状態をパラメーターとしてスタックに渡した場合にはるかに簡単になることを実現しています。printMessageにメッセージをパラメーターとして受け取らせると、これらすべての問題が解決されます。

public void printMessage(final String message){
    System.out.println( message.toUpperCase( ) );
}

これで、実際にnullが渡された場合にのみ対処する必要があります。

于 2012-07-31T00:10:05.250 に答える
1

読み取りをロックしていますが、値を更新するときにロックする必要があります。スレッドセーフを実現するには、ロックに両方を含める必要があります。つまり、これらをロックします。

handler.message = "Hello world! ";
handler.printMessage( );
于 2012-07-30T16:47:28.463 に答える
1

メッセージの設定は、コード内でスレッドセーフではありません...

public class MyClass implements Runnable{

BaseHandler handler = new BaseHandler();

public synchronized void go(){

          for (int i = 0; i < 50000; i++) {

                    handler.message = "Hello world! ";
                    handler.printMessage( );
                }                




   }

}

Base と BaseHandler をそのままにしましょう... Test クラスにいくつかの変更を加えて

public class Test {   
    public static void main(String[] args){       


            Thread worker = new Thread(MyClass);
            worker.setName(String.valueOf(i));
            worker.start();
        }
    }
于 2012-07-30T16:48:35.260 に答える
1

共有可変オブジェクトに対する読み取り操作と書き込み操作の両方で、スレッド セーフを確保する必要があります。あなたの場合、安全でない書き込みを行っています。また、読み取りと書き込みは同じロックを共有する必要があります。

public synchronized void setMessage(String msg) {
        this.message = msg;
    }

    public synchronized String getMessage() {
        return message;
    }
}

ここでは、暗黙的にオブジェクト インスタンスをロックとして使用していることに注意してください。同じ可変オブジェクトのロックとして異なるオブジェクトを使用するのはよくある間違いです。

次に、BaseHandler クラスは次のようになります。

public class BaseHandler extends Base {

    public synchronized void printMessage( ) {
        if (getMessage()!=null) {
            System.out.println( getMessage().toUpperCase( ) );
            setMessage(null);
        }
    }
}

これら 2 つのメソッドにより、Base-BaseHandler クラス階層がすべてのクライアントに対してスレッドセーフになります。これは、オブジェクトを使用するクライアントが同期を使用する必要がないことを意味します。

于 2012-07-30T16:50:38.220 に答える