Java Concurrency JDK 1.6:ビジー待機はシグナリングよりも優れていますか? 効果的なJava#51

concurrency java multithreading synchronization
Java Concurrency JDK 1.6:ビジー待機はシグナリングよりも優れていますか? 効果的なJava#51

Joshua Blochの「Effective Java」のItem 51は、スレッドスケジューラに依存することではなく、スレッドを不必要に実行可能状態に保つこともありません。 引用テキスト:

_
実行可能なスレッドの数を抑えるための主な手法は、各スレッドに少量の作業を行わせてから、Object.waitを使用して一定の条件を待機させるか、Thread.sleepを使用して一定の時間が経過するのを待つことです。 スレッドは、何かが起こるのを待っているデータ構造を繰り返しチェックして、ビジー待機してはいけません。 プログラムをスケジューラの変動に対して脆弱にするだけでなく、ビジー待機はプロセッサの負荷を大幅に増加させ、同じマシン上で他のプロセスが達成できる有用な作業の量を減らすことができます。
_

そして次に、ビジーな待機とシグナルの適切な使用のマイクロベンチマークを示します。 本では、ビジー待機は1秒あたり17往復しますが、待機/通知バージョンは1秒あたり23,000往復します。

ただし、JDK 1.6で同じベンチマークを試したところ、逆のことがわかりました-ビジー待機は760Kラウンドトリップ/秒でしたが、待機/通知バージョンは53.3Kラウンドトリップ/秒でした-つまり、待機/通知は〜1400でした倍速くなりますが、最大13倍遅くなりますか?

私はビジー待機が良くなく、シグナリングがまだ良いことを理解しています-CPU使用率はビジー待機バージョンで約50%ですが、待機/通知バージョンでは約30%のままです-しかし、数字を説明するものはありますか?

うまくいけば、Win 7 x64(コアi5)でJDK1.6(32ビット)を実行しています。

  • UPDATE *:以下のソース。 忙しい作業台を実行するには、PingPongQueueの基本クラスをBusyWorkQueueに変更しますimport java.util.LinkedList; import java.util.List;

abstract class SignalWorkQueue {
    private final List queue = new LinkedList();
    private boolean stopped = false;

    protected SignalWorkQueue() { new WorkerThread().start(); }

    public final void enqueue(Object workItem) {
        synchronized (queue) {
            queue.add(workItem);
            queue.notify();
        }
    }

    public final void stop()  {
        synchronized (queue) {
            stopped = true;
            queue.notify();
        }
    }
    protected abstract void processItem(Object workItem)
        throws InterruptedException;
    private class WorkerThread extends Thread {
        public void run() {
            while (true) {  // Main loop
                Object workItem = null;
                synchronized (queue) {
                    try {
                        while (queue.isEmpty() && !stopped)
                            queue.wait();
                    } catch (InterruptedException e) {
                        return;
                    }
                    if (stopped)
                        return;
                    workItem = queue.remove(0);
                }
                try {
                    processItem(workItem); // No lock held
                } catch (InterruptedException e) {
                    return;
                }
            }
        }
    }
}

// HORRIBLE PROGRAM - uses busy-wait instead of Object.wait!
abstract class BusyWorkQueue {
    private final List queue = new LinkedList();
    private boolean stopped = false;

    protected BusyWorkQueue() {
        new WorkerThread().start();
    }

    public final void enqueue(Object workItem) {
        synchronized (queue) {
            queue.add(workItem);
        }
    }

    public final void stop() {
        synchronized (queue) {
            stopped = true;
        }
    }

    protected abstract void processItem(Object workItem)
            throws InterruptedException;

    private class WorkerThread extends Thread {
        public void run() {
            final Object QUEUE_IS_EMPTY = new Object();
            while (true) { // Main loop
                Object workItem = QUEUE_IS_EMPTY;
                synchronized (queue) {
                    if (stopped)
                        return;
                    if (!queue.isEmpty())
                        workItem = queue.remove(0);
                }

                if (workItem != QUEUE_IS_EMPTY) {
                    try {
                        processItem(workItem);
                    } catch (InterruptedException e) {
                        return;
                    }
                }
            }
        }
    }
}

class PingPongQueue extends SignalWorkQueue {
    volatile int count = 0;

    protected void processItem(final Object sender) {
        count++;
        SignalWorkQueue recipient = (SignalWorkQueue) sender;
        recipient.enqueue(this);
    }
}

public class WaitQueuePerf {
    public static void main(String[] args) {
        PingPongQueue q1 = new PingPongQueue();
        PingPongQueue q2 = new PingPongQueue();
        q1.enqueue(q2); // Kick-start the system

        // Give the system 10 seconds to warm up
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
        }

        // Measure the number of round trips in 10 seconds
        int count = q1.count;
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
        }
        System.out.println(q1.count - count);

        q1.stop();
        q2.stop();
    }
}

  6  1


ベストアンサー

テストでは、キューは新しいアイテムを継続的に取得するため、ビジー待機は実際の待機をほとんど行いません。

キューが1ミリ秒ごとに1つの新しいアイテムを取得する場合、busy-waitがほとんど時間を費やして何もせずにCPUを消費することがわかります。 アプリケーションの他の部分の速度が低下します。

だからそれは違います。 ユーザー入力の待機で忙しい場合、それは間違いです。 AtomicIntegerのようなロックレスデータ構造でのビジー待機は間違いなく優れています。

6


はい、ビジーウェイトはより迅速に応答し、より多くのループを実行しますが、ポイントはシステム全体に不均衡に重い負荷をかけることだったと思います。

1000個のビジー待機スレッドと1000個の待機/通知スレッドを実行して、合計スループットを確認してください。

あなたが観察した違いは、おそらく、人々がすべきことではなく、人々がすることのためにコンパイラを最適化することです。 Sunは常にそうしています。 この本の最初のベンチマークは、Sunが修正したスケジューラーのバグによるものかもしれません。

3


これは、スレッドの量と競合の程度に依存します。頻繁に発生したり、多くのCPUサイクルを消費したりすると、ビジー待機は悪いです。

しかし、アトミック整数(AtomicInteger、AtomicIntegerArray …​)は、整数またはint []を同期するよりも優れており、スレッドもビジー待機を実行します。

java.util.concurrentパッケージを使用し、場合によってはConcurrentLinkedQueueをできるだけ頻繁に使用します

1


忙しい待機は必ずしも悪いことではありません。 Java同期プリミティブを使用した「適切な」(低レベルの)方法は、汎用メカニズムの実装に必要なブックキーピングのオーバーヘッド(多くの場合かなり)を運び、ほとんどのシナリオでかなりよく機能します。 一方、ビジー待機は非常に軽量であり、状況によっては、万能同期よりも大幅に改善される可能性があります。 ビジーウェイトのみに基づく同期は、一般的な設定では間違いなく間違いありませんが、場合によっては非常に便利です。 Javaだけでなく、スピンロック(ビジー待機ベースのロックの仮名)は、データベースサーバーなどで広く使用されています。

実際、java.util.concurrentパッケージのソースを調べてみると、「トリッキー」な一見脆弱なコードを含む多くの場所が見つかります。 `SynchronousQueue`が良い例だと思います(JDKディストリビューションまたはhttp://hg.openjdk.java.net/jdk7/jdk7/jdk/file/00cd9dc3c2b5/src/share/classes/javaのソースを見ることができます) /util/concurrent/SynchronousQueue.java [こちら]、OpenJDKとOracleは同じ実装を使用しているようです)。 ビジー待機は最適化として使用されます-一定量の「スピン」の後、スレッドは適切な「スリープ」に入ります。 それとは別に、揮発性のピギーバック、CPUの数に依存するスピンのしきい値など、他にもいくつかの利点があります。 本当に…​ 効率的で低レベルの並行性を実装するために必要なものを示しているという点で、照らします。 さらに良いことに、*コード*自体は本当にきれいで、よく文書化されており、一般に高品質です。

0


タイトルとURLをコピーしました