0

Oracle のドキュメントを注意深く読みましたが、問題の設計パターンの解決策が見つかりませんでした。2 つの匿名スレッドがあり、一方が他方に通知する必要があります。

public static void main(String[] args) {
    MyClass obj = new MyClass();
    obj.a();
    obj.b();

}

MyClass には 2 つの異なる関数があり、それぞれが匿名スレッドを起動します。B の人は、妻の A に起こされることを期待しています。

public class MyClass{

    public MyClass(){

    }

    public void a() {
        new Thread(new Runnable(){

            @Override
            public synchronized void run() {
                System.out.println("A: I am going to sleep");
                try {
                    Thread.sleep(1000);
                    System.out.println("A: I slept one full day. Feels great.");
                    System.out.println("A: Hey B, wake up!");
                    notifyAll();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }


            }
        }).start();


    }

    public void b() {
        new  Thread(new Runnable(){

            @Override
            public synchronized void run() {
                System.out.println("B: I am  going to sleep. A, please wake me up.");
                try {
                    wait();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println("B: Thank you A for waking me up!");


            }
        }).start();


    }

}

残念ながら、B は永遠に眠ってしまい、妻の A によって起こされることはありませんでした。

プログラムの出力:

A: I am going to sleep
B: I am  going to sleep. A, please wake me up.
A: I slept one full day. Feels great.
A: Hey B, wake up!

A と B は 2 つの異なる匿名スレッド オブジェクトで実行されているため、A は他の A にのみ通知できることを理解しています (ベッドには他の妻がいないため、ここでは通知機能は役に立ちません)。

この問題の正しい設計パターンは何ですか?

4

6 に答える 6

1

両方のスレッドは、同じセマフォオブジェクトを使用してロックする必要があります。

現在、コード内のロックは 2 つの異なるオブジェクトにあります。Runnableによって作成されたaはそれ自体にロックがあり、 と同じでbあるため、呼び出したときnotifyAllに、ロックが通知されるのを待っているオブジェクトはありません。

Thread.sleep同期ブロックの内部にも問題があります。

synchronizedキーワードが次のように使用されたときにロックが取得されるようにコードを変更します。

public void a()
{
  new Thread(
    new Runnable()
    {
      @Override
      public void run()
      {
        try
        {
          System.out.println("A: I am going to sleep");
          Thread.sleep(1000);
        }
        catch (InterruptedException e)
        {
          e.printStackTrace();
        }

        synchronized(MyClass.this)
        {
          System.out.println("A: I slept one full day. Feels great.");
          System.out.println("A: Hey B, wake up!");
          MyClass.this.notifyAll();
        }
      }
    }
  ).start();
}

public void b()
{
  new Thread(
    new Runnable()
    {
      @Override
      public void run()
      {
        synchronized(MyClass.this)
        {
          System.out.println("B: I am  going to sleep. A, please wake me up.");

          try
          {
            MyClass.this.wait();
          }
          catch (InterruptedException e)
          {
            e.printStackTrace();
          }

          System.out.println("B: Thank you A for waking me up!");
        }
      }
    }
  ).start();
}
于 2013-07-25T11:54:19.497 に答える
0

おそらくクラス変数として、これらのスレッド間で共有されるReentrantLockが必要になります。スレッド A が最初にロックをロックし、次にスリープ状態になるためにスレッド B がロックします。スレッド A は、ロックを解除してスレッド B を目覚めさせます。これにはセマフォを使用することもできます。

于 2013-07-25T11:49:38.337 に答える
0

基本的なポイントはwait()、スレッド同期を行うために単一のオブジェクトモニターで呼び出す必要がありますnotify()notifyAll()私はこのようなことをしただろう

私のコードでは、MyClasshasa()b()instance method synchronized. したがって、これらのメソッドが呼び出されるインスタンスは、暗黙的なモニターになります。の同じインスタンスMyClass(つまりobj) を 2 つのRunnable実装と共有しています

public class MyClass{

    public MyClass(){

    }

    public synchronized void a() {
        System.out.println("A: I am going to sleep");
        try {
            Thread.sleep(5000);
            wait();
            System.out.println("A: I slept one full day. Feels great.");
            System.out.println("A: Hey B, wake up!");
            notifyAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    public synchronized void b() {
        System.out.println("B: I am  going to sleep. A, please wake me up.");
        notifyAll();
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("B: Thank you A for waking me up!");

    }

    public static void main(String [] args) {
        MyClass obj = new MyClass();

        Thread t1 = new Thread(new RunnableImpl(obj, true));
        Thread t2 = new Thread(new RunnableImpl(obj, false));
        t1.start();
        t2.start();
    }

}

class RunnableImpl implements Runnable {

    boolean callA;
    MyClass obj;

    public RunnableImpl(MyClass obj, boolean callA) {
        this.callA = callA;
        this.obj = obj;
    }


    @Override
    public void run() {
        if(callA) {
            obj.a();
        }
        else {
            obj.b();
        }
    }

}
于 2013-07-25T11:49:51.943 に答える
0

wait()/notify() メソッドを呼び出すには、スレッドによって共有される共通のオブジェクトが必要です。thisどちらの場合も独自の Thread オブジェクトであるオブジェクトでそれらを呼び出しています。

また、共通オブジェクトでも同期する必要があることに注意してください。したがって、 run() メソッドに同期を設定することはできません。

于 2013-07-25T11:49:54.953 に答える
0

コードに 2 つの問題があります。

  1. 他の人が示唆したように。通知と待機を使用するには、同じロックを取得する必要があります。それぞれのスレッドインスタンスである待機と通知に異なるオブジェクトを使用しています。以下のコードは MyClass.this を使用して実行されます

    {待つ();を試してください。キャッチ(InterruptedException e){

  2. 正しいロックを使用している場合でも、コードには別の問題があります。スレッド A で Thread.sleep(1000) によって遭遇しようとしていると思います。この問題は Missed Notifications と呼ばれます。つまり、threadB がその wait() メソッドを実行する前に threadA が完了する可能性があるため、threadB が無限にスリープ状態になります。

上記の問題の両方の解決策は、ラッチを使用することです。CountDownLatch を試してください 以下を参照してください

import java.util.concurrent.CountDownLatch;

public class MyClass{

    CountDownLatch latch = new CountDownLatch(1);

    public MyClass(){

    }

    public void a() {
        new Thread(new Runnable(){
            @Override
            public void run() {
                System.out.println("A: I am going to sleep");
                System.out.println("A: I slept one full day. Feels great.");
                System.out.println("A: Hey B, wake up!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                latch.countDown();
            }
        }).start();
    }

    public void b() {
        new  Thread(new Runnable(){
            @Override
            public  void run() {
                System.out.println("B: I am  going to sleep. A, please wake me up.");
                try {
                    latch.await();
                } catch (InterruptedException e) {}
                System.out.println("B: Thank you A for waking me up!");
            }
        }).start();
    }

    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.a();
        obj.b();
    }

}
于 2013-08-02T10:01:33.197 に答える
0

あるスレッドを別のスレッドから起動できるようにするには、共通のオブジェクトと同期する必要があります。たとえば、スレッドが呼び出される MyClass オブジェクトを使用できます。

public void a() {
    new Thread(new Runnable(){

        @Override
        public synchronized void run() {
            System.out.println("A: I am going to sleep");
            synchronized(MyClass.this)
            {
                try {
                    Thread.sleep(1000);
                    System.out.println("A: I slept one full day. Feels great.");
                    System.out.println("A: Hey B, wake up!");
                    MyClass.this.notifyAll();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }).start();


}

public void b() {
    new  Thread(new Runnable(){

        @Override
        public synchronized void run() {
            System.out.println("B: I am  going to sleep. A, please wake me up.");
            synchronized(MyClass.this)
            {
                System.out.println("B: Thank you A for waking me up!");
            }
        }
    }).start();


}

これによりa()、 のスレッドがロックを取得し、1000 ミリ秒スリープします。その間に が呼び出されますが、そのスレッドは、 のスレッドがロックを解除するb()まで待機してから印刷する必要があります。a()Thank you for waking me up

a()これは、常にbeforeを呼び出す場合に機能しますb()。それ以外の場合、b()が最初にロックを取得すると、 のThank you for waking me up前に実行されa() sleepます。

于 2013-07-25T11:54:17.137 に答える