0

これはある種の Java Puzzler で、私が偶然見つけたもので、実際には説明できません。たぶん誰かができますか?

次のプログラムは、しばらくするとハングします。2 回出力した後、80 回出力した後ということもありますが、ほとんどの場合、正しく終了する前に発生します。初めて実行しない場合は、数回実行する必要がある場合があります。

public class Main {
    public static void main(String[] args) {
        final WorkerThread[] threads = new WorkerThread[]{ new WorkerThread("Ping!"), new WorkerThread("Pong!") };
        threads[0].start();
        threads[1].start();

        Runnable work = new Runnable() {
            private int counter = 0;
            public void run() {
                System.out.println(counter + " : " + Thread.currentThread().getName());
                threads[counter++ % 2].setWork(this);
                if (counter == 100) {
                    System.exit(1);
                }
            }
        };

        work.run();
    }
}

class WorkerThread extends Thread {
    private Runnable workToDo;

    public WorkerThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        while (true){
            if (workToDo != null) {
                workToDo.run();
                workToDo = null;
            }
        }
    }

    public void setWork(Runnable newWork) {
        this.workToDo = newWork;
    }
}

ここで、ビジーな待機ループが一般的には良い考えではないことは明らかです。しかし、これは改善ではなく、何が起こっているのかを理解することです。

フィールドが に設定されている場合、またはフィールドが に設定されているWorkerThread.setWork()場合、すべてが期待どおりに機能するため、メモリの問題が疑われます。synchronizedWorkerThread.workToDovolatile

しかし、なぜそれが起こっているのでしょうか?デバッグは役に立ちません。ステップスルーを開始すると、すべてが期待どおりに動作します。

説明をいただければ幸いです。

4

3 に答える 3

2
  1. 最初の問題は、スレッドRunnable workToDoからを設定し、mainそれを同期せずに 2 つのフォークされたスレッドで読み取ることです。複数のスレッドでフィールドを変更するときはいつでも、volatileまたは someoneとしてマークする必要がありますsynchronized

    private volatile Runnable workToDo;
    
  2. また、複数のスレッドが実行しているため、counter++これもsynchronized. そんな方におすすめAtomicIntegerです。

    private AtomicInteger counter = new AtomicInteger(0);
    ...
    threads[counter.incrementAndGet() % 2].setWork(this);
    
  3. しかし、本当の問題は競合状態の 1 つかもしれないと思います。両方のスレッドが を に設定してworkToDoから、Runnable両方を返して に戻すことができるnullため、永遠にスピンします。それを修正する方法がわかりません。

    1. threads[0] has it's `workToDo` set to the runnable.  It calls `run()`.
    2. at the same time threads[1] also calls `run()`.
    3. threads[0] sets the `workToDo` on itself and threads[1] to be the runnable.
    4. at the same time threads[1] does the same thing.
    5. threads[0] returns from the `run()` method and sets `workToDo` to be `null`.
    6. threads[1] returns from the `run()` method and sets `workToDo` to be `null`.
    7. They spin forever...
    

そして、おっしゃる通り、スピン ループはクレイジーですが、これはデモ スレッド プログラムだと思います。

于 2013-04-23T21:34:35.163 に答える
1

問題は次の行の間で発生します。

workToDo.run();
workToDo = null;

次の一連のイベントが発生するとします。

- Original Runnable runs.  "Ping!".setWork() called
- Ping! thread realizes workToDo != null, calls run(), the stops between those two lines
  - "Pong!".setWork() called
- Pong! thread realizes workToDo != null, calls run()
  - "Ping!".setWork() called
- Ping! thread resumes, sets workToDo = null, ignorantly discarding the new value
- Both threads now have workToDo = null, and the counter is frozen at 2,...,80
  Program hangs
于 2013-04-23T21:44:55.227 に答える
0

私の2セント....

import java.util.concurrent.atomic.AtomicReference;

class WorkerThread extends Thread {
    private AtomicReference<Runnable> work;

    public WorkerThread(String name) {
        super(name);
        work = new AtomicReference<Runnable>();
    }

    @Override
    public void run() {
        while (true){
            Runnable workToDo = work.getAndSet(null);
            if ( workToDo != null ) {
                workToDo.run();
            }
        }
    }

    public void setWork(Runnable newWork) {
        this.work.set(newWork);
    }
}
于 2014-08-14T16:41:40.057 に答える