5

Java ME で単純なブロッキング キューを実装しようとしています。JavaME API では、Java SE の並行ユーティリティが利用できないため、昔のように wait-notify を使用する必要があります。

これは私の暫定的な実装です。私のプロジェクトには複数のプロデューサーがあり、コンシューマーは 1 つしかないため、notify代わりに使用しています。notifyAll参照を無駄にしますが、読みやすさを向上させるために意図的に wait-notify のオブジェクトを使用しました。

    import java.util.Vector;

    public class BlockingQueue {    
        private Vector queue = new Vector();
        private Object queueLock = new Object();    

        public void put(Object o){
            synchronized(queueLock){
                queue.addElement(o);
                queueLock.notify();
            }       
        }

        public Object take(){
            Object ret = null;
            synchronized (queueLock) {
                while (queue.isEmpty()){
                    try {
                        queueLock.wait();
                    } catch (InterruptedException e) {}
                }

                ret = queue.elementAt(0);
                queue.removeElementAt(0);
            }
            return ret;
        }
    }

私の主な質問は、put方法についてです。ブロックqueue.addElementからラインを出してもらえますか?synchronizedその場合、パフォーマンスは向上しますか?

また、同じことが にも当てはまりますtake: から 2 つの操作を実行できqueueますsynchronized blockか?

他に可能な最適化はありますか?

編集:
@Raam が正しく指摘したように、コンシューマー スレッドは、wait. では、これを防ぐための代替手段は何ですか?(注: JavaME には、Java SE からのこれらすべての優れたクラスはありません。古い Java v1.2 と考えてください)

4

4 に答える 4

1

synchronized共有状態へのアクセスを保護し、原子性を確保するために使用されます。

のメソッドVectorは既に同期されているため、Vector独自の共有状態自体を保護することに注意してください。したがって、同期ブロックは、操作の原子性を確保するためにのみ必要です。

メソッドの正確性には原子性が重要であるため、メソッドqueueの同期ブロックから操作を移動することはできません。take()しかし、私が理解している限り、put()メソッド内の同期ブロックからキュー操作を移動できます (うまくいかない状況は想像できません)。

queueLockただし、上記の推論は純粋に理論的なものです。すべての場合において、同期が二重にVector行われるためqueueです。したがって、提案された最適化は意味がありません。その正確さは、その二重同期の存在に依存します。

二重同期を回避するには、同期も行う必要がありqueueます。

synchronized (queue) { ... }

ArrayList別のオプションとして、の代わりに非同期コレクション ( など) を使用することもできますがVector、JavaME はそれをサポートしていません。この場合、同期ブロックは非同期コレクションの共有状態も保護するため、提案された最適化を使用することはできません。

于 2012-04-26T09:09:00.153 に答える
1

Vector クラスは、スレッドセーフであるという保証はありません。あなたが行ったように、アクセスを同期する必要があります。現在のソリューションにパフォーマンスの問題があるという証拠がない限り、私は心配しません。

余談ですが、複数のコンシューマーをサポートするのnotifyAllではなく、使用しても問題はないと思います。notify

于 2012-04-26T08:54:13.987 に答える
0

特にガベージコレクションが原因でパフォーマンスの問題が発生しない限り、キューを実装するためにベクターではなくリンクリストを使用します(先入れ先出し)。

また、プロジェクト(または別のプロジェクト)が複数のコンシューマーを取得するときに再利用されるコードを記述します。その場合でも、Java言語仕様はモニターを実装する方法を課していないことに注意する必要があります。実際には、これは、通知を受けるコンシューマスレッドを制御しないことを意味します(既存のJava仮想マシンの半分はFIFOモデルを使用してモニターを実装し、残りの半分はLIFOモデルを使用してモニターを実装します)

また、ブロッキングクラスを使用している人は誰でもInterruptedExceptionを処理することになっていると思います。結局のところ、クライアントコードはnullオブジェクトリターンを処理する必要があります。

だから、このようなもの:


/*package*/ class LinkedObject {

    private Object iCurrentObject = null;
    private LinkedObject iNextLinkedObject = null;

    LinkedObject(Object aNewObject, LinkedObject aNextLinkedObject) {
        iCurrentObject = aNewObject;
        iNextLinkedObject = aNextLinkedObject;
    }

    Object getCurrentObject() {
        return iCurrentObject;
    }

    LinkedObject getNextLinkedObject() {
        return iNextLinkedObject;
    }
}

public class BlockingQueue {
    private LinkedObject iLinkedListContainer = null;
    private Object iQueueLock = new Object();
    private int iBlockedThreadCount = 0;

    public void appendObject(Object aNewObject) {
        synchronized(iQueueLock) {
            iLinkedListContainer = new iLinkedListContainer(aNewObject, iLinkedListContainer);
            if(iBlockedThreadCount > 0) {
                iQueueLock.notify();//one at a time because we only appended one object
            }
        } //synchonized(iQueueLock)
    }

    public Object getFirstObject() throws InterruptedException {
        Object result = null;
        synchronized(iQueueLock) {
            if(null == iLinkedListContainer) {
                ++iBlockedThreadCount;
                try {
                    iQueueLock.wait();
                    --iBlockedThreadCount; // instead of having a "finally" statement
                } catch (InterruptedException iex) {
                    --iBlockedThreadCount;
                    throw iex;
                }
            }
            result = iLinkedListcontainer.getCurrentObject();
            iLinkedListContainer = iLinkedListContainer.getNextLinkedObject();
            if((iBlockedThreadCount > 0)  && (null != iLinkedListContainer )) {
                iQueueLock.notify();
            }
        }//synchronized(iQueueLock)
        return result;
    }

}

同期されたブロックに入れるコードを少なくしようとすると、クラスは正しくなくなると思います。

于 2012-04-26T22:35:05.500 に答える
-2

このアプローチにはいくつかの問題があるようです。キューに要素がある場合でも、コンシューマーが通知を見逃してキューで待機する可能性があるシナリオがあります。次の順序を時系列で検討してください

T1-コンシューマーはqueueLockを取得してから、待機を呼び出します。Waitはロックを解放し、スレッドに通知を待機させます

T2-あるプロデューサーがqueueLockを取得し、キューに要素を追加して、notifyを呼び出します

T3-コンシューマースレッドに通知され、queueLockを取得しようとしますが、同時に別のプロデューサーが来るため失敗します。(notify java docから-覚醒したスレッドは、このオブジェクトで同期するためにアクティブに競合している可能性のある他のスレッドと通常の方法で競合します。たとえば、覚醒したスレッドは、次にロックするスレッドであるという信頼できる特権や不利な点を享受しません。このオブジェクト。)

T4- 2番目のプロデューサーが別の要素を追加し、notifyを呼び出します。コンシューマーがqueueLockを待機しているため、この通知は失われます。

したがって、理論的には、コンシューマーが飢えている可能性があります(queueLockを取得しようとして永遠に立ち往生します)。また、複数のプロデューサーがキューに読み取られてキューから削除されていない要素をキューに追加するというメモリの問題が発生する可能性があります。

私が提案するいくつかの変更は次のとおりです-

  • キューに追加できるアイテムの数の上限を維持します。
  • 消費者が常にすべての要素を読むようにします。これは、生産者/消費者問題をどのようにコーディングできるかを示すプログラムです。
于 2012-04-26T09:53:47.480 に答える