web-dev-qa-db-ja.com

java)に独自のブロッキングキューを実装する

私はこの質問が以前に何度も尋ねられ、答えられたことを知っています、しかし私はインターネットの周りに見られる例のトリックを理解することができませんでした this または that 1。

これらのソリューションはどちらも、put()メソッドでnotifyAll待機中のスレッドに対するブロッキングキューの配列/キュー/リンクリストの空をチェックし、get()メソッドではその逆をチェックします。 2番目のリンクの コメント はこの状況を強調し、それは必要ではないと述べています。

したがって、問題は次のとおりです。キューが空かどうかを確認するのも少し奇妙に思えます|待機中のすべてのスレッドに通知するためにfull。何か案は?

前もって感謝します。

14
tugcem

これは今では古い質問だと思いますが、質問と回答を読んだ後、私は自分自身を助けることができませんでした。これがお役に立てば幸いです。

他の待機中のスレッドに通知する前に、キューが実際にいっぱいか空かを確認することに関して、put (T t)T get()の両方がsynchronizedメソッドである何かが欠落しています。つまり、一度に1つのスレッドのみがこれらのメソッドの1つに入ることができますが、これはそれらが一緒に機能することを妨げることはありません。したがって、thread-aがput (T t)メソッドに入った場合でも、別のthread-bが入り、開始できます。スレッドaがT get()を終了する前に、put (T t)メソッドの命令を実行するため、このdouble-checking設計により、開発者は少し安全に感じることができます。将来のCPUコンテキストの切り替えが発生するかどうか、または発生するかどうかを確認します。

より適切で推奨されるアプローチは、Reentrant LocksおよびConditionsを使用することです。

//これからソースコードを編集しました link

Condition isFullCondition;
Condition isEmptyCondition;
Lock lock;

public BQueue() {
    this(Integer.MAX_VALUE);
}

public BQueue(int limit) {
    this.limit = limit;
    lock = new ReentrantLock();
    isFullCondition = lock.newCondition();
    isEmptyCondition = lock.newCondition();
}

public void put (T t) {
    lock.lock();
    try {
       while (isFull()) {
            try {
                isFullCondition.await();
            } catch (InterruptedException ex) {}
        }
        q.add(t);
        isEmptyCondition.signalAll();
    } finally {
        lock.unlock();
    }
 }

public T get() {
    T t = null;
    lock.lock();
    try {
        while (isEmpty()) {
            try {
                isEmptyCondition.await();
            } catch (InterruptedException ex) {}
        }
        t = q.poll();
        isFullCondition.signalAll();
    } finally { 
        lock.unlock();
    }
    return t;
}

このアプローチを使用すると、lockオブジェクトが2つのメソッド間で共有されるため、double checkingは必要ありません。つまり、作成する同期メソッドとは異なり、一度に1つのスレッドaまたはbのみがこれらのメソッドのいずれかに入ることができます。異なるモニター、およびキューがいっぱいであるために待機しているスレッドのみが、より多くのスペースがあるときに通知されます。キューが空であるために待機しているスレッドについても同じことが言えます。これにより、CPU使用率が向上します。あなたはソースコードでより詳細な例を見つけることができます ここ

20
Ayesh Qumhieh

著者がnotifyAll()を使用した理由は単純です。必要かどうかの手がかりがなかったため、「より安全な」オプションを選択しました。

上記の例では、追加された単一の要素ごとにnotify()を呼び出すだけで十分であり、すべての状況で待機している単一のスレッドのみを処理できます。

キューにもaddAll(Collection<T> list)のように1つのステップで複数の要素を追加するオプションがある場合、これはより明白になります。この場合、正確には、空のリストで待機している複数のスレッドが提供される可能性があります。要素と同じ数のスレッドが追加されました。

ただし、notifyAll()は、特別な単一要素の場合に余分なオーバーヘッドを引き起こします。これは、多くのスレッドが不必要にウェイクアップされるため、再度スリープ状態にする必要があり、その間にキューアクセスがブロックされるためです。したがって、notifyAll()notify()に置き換えると、速度が向上しますこの特別な場合

しかし、not wait/notifyを使用して同期しますが、代わりに並行パッケージを使用すると、スマートなwait/notify実装よりもはるかに速度が向上します。

1
TwoThe

論理的には、notifyAll()の前に追加のチェックを行っても害はないと思います。

キューに何かを入れたり取り出したりしたら、単純にnotifyAll()できます。すべてが引き続き機能し、コードは短くなります。ただし、notifyAll()を呼び出す前に、誰かが待機している可能性があるかどうかをチェックすること(キューの境界に到達するかどうかをチェックすることによって)も害はありません。この余分なロジックにより、不要なnotifyAll()呼び出しが節約されます。

短くてクリーンなコードが必要か、コードをより効率的に実行するかによって異なります。 (notifyAll()の実装を調べたことがありません。待機している人がいない場合に安価な操作である場合、とにかくその追加のチェックでパフォーマンスの向上が明らかでない可能性があります)

1
Adrian Shum