7

私はJavaマルチスレッドプログラミングを学んでいます。次のロジックがあります。

クラスAがあるとします

class A {
    ConcurrentMap<K, V> map;

    public void someMethod1 () {
        // operation 1 on map
        // operation 2 on map
    }

    public void someMethod2 () {
        // operation 3 on map
        // operation 4 on map
    }
}

これで、「someMethod1」または「someMethod2」での操作の同期は必要なくなりました。これは、同時に「someMethod1」を呼び出す 2 つのスレッドがある場合、これらの操作をシリアル化する必要がないことを意味します (ConcurrentMap がジョブを実行するため)。

ただし、「someMethod1」と「someMethod2」が相互のミューテックスであることを願っています。つまり、一部のスレッドが「someMethod1」を実行している場合、別のスレッドは「someMethod2」に入るのを待つ必要があります(ただし、別のスレッドは「someMethod1」に入ることが許可されている必要があります)。

要するに、「someMethod1」と「someMethod2」を相互のミューテックスではなく、相互のミューテックスにする方法はありますか?

私の質問を十分に明確に述べたことを願っています...

ありがとう!

4

5 に答える 5

4

より高いレベルの構成でいくつかの試みを試みましたが、何も思い浮かびませんでした. これは、低レベルの API に落とし込む機会になると思います。

編集:私は実際にあなたが本質的にトリッキーな問題を設定しようとしていると思います(最後の段落の2番目から2番目を参照)そしておそらく必要ではありません(最後の段落を参照)。しかし、そうは言っても、これを行う方法は次のとおりです。この回答の最後に色の解説を残します.

private int someMethod1Invocations = 0;
private int someMethod2Invocations = 0;

public void someMethod1() {
    synchronized(this) {
        // Wait for there to be no someMethod2 invocations -- but
        // don't wait on any someMethod1 invocations.
        // Once all someMethod2s are done, increment someMethod1Invocations
        // to signify that we're running, and proceed
        while (someMethod2Invocations > 0)
            wait();
        someMethod1Invocations++;
    }

    // your code here

    synchronized (this) {
        // We're done with this method, so decrement someMethod1Invocations
        // and wake up any threads that were waiting for that to hit 0.
        someMethod1Invocations--;
        notifyAll();
    }
}

public void someMethod2() {
    // comments are all ditto the above
    synchronized(this) {
        while (someMethod1Invocations > 0)
            wait();
        someMethod2Invocations++;
    }

    // your code here
    synchronized(this) {
        someMethod2Invocations--;
        notifyAll();
    }
}

上記の明らかな問題の 1 つは、スレッドの枯渇につながる可能性があることです。たとえば、someMethod1()が実行中 (および s をブロックsomeMethod2()) であり、それが終了しようとしているときに、別のスレッドがやってきて を呼び出しますsomeMethod1()。それは問題なく進行し、終了するとすぐに別のスレッドが開始someMethod1()されます。このシナリオでsomeMethod2()は、実行する機会はありません。これは、実際には上記のコードの直接のバグではありません。これはまさに設計ニーズの問題であり、優れたソリューションが積極的に解決する必要がある問題です。公正なAbstractQueuedSynchronizerでこのトリックを実行できると思いますが、それは読者に任せた演習です。:)

最後に、意見を差し挟まざるを得ません。ConcurrentHashMap操作が非常に高速であることを考えると、両方のメソッドに単一のミューテックスを配置して、それを終了するだけの方がよい場合があります。そうです、スレッドは invoke をキューに入れる必要がありますsomeMethod1()が、各スレッドはそのターンを非常に迅速に終了します (したがって、他のスレッドを続行させます)。それは問題ではないはずです。

于 2013-01-03T07:20:51.890 に答える
2

これはおそらく機能しません(コメントを参照)-情報のために残してください。


1 つの方法は、Semaphoresを使用することです。

  • sem1method1 にリンクされた 1 つのパーミットを持つ 1つのセマフォ
  • sem2method2 にリンクされた 1 つのパーミットを持つ 1つのセマフォ

method1 に入るとき、sem2 の許可を取得しようとし、利用可能な場合はすぐに解放します。

実装例については、この投稿を参照してください。

注: コードでは、ConcurrentMap がスレッド セーフであっても、操作 1 と操作 2 (たとえば) はアトミックではないため、シナリオでは次のインターリーブが可能です。

  • スレッド 1 は操作 1 を実行します
  • スレッド 2 は操作 1 を実行します
  • スレッド 2 は操作 2 を実行します
  • スレッド 1 は操作 2 を実行します
于 2013-01-03T05:47:58.600 に答える
2

これはうまくいくはずだと思います

class A {
    Lock lock = new Lock();

    private static class Lock {
        int m1;
        int m2;
    }

    public void someMethod1() throws InterruptedException {
        synchronized (lock) {
            while (lock.m2 > 0) {
                lock.wait();
            }
            lock.m1++;
        }

        // someMethod1 and someMethod2 cannot be here simultaneously

        synchronized (lock) {
            lock.m1--;
            lock.notifyAll();
        }
    }

    public void someMethod2() throws InterruptedException {
        synchronized (lock) {
            while (lock.m1 > 0) {
                lock.wait();
            }
            lock.m2++;
        }

        // someMethod1 and someMethod2 cannot be here simultaneously

        synchronized (lock) {
            lock.m2--;
            lock.notifyAll();
        }
    }
}
于 2013-01-03T07:23:47.643 に答える
0

カスタムシンクロナイザーなしではこれを行うことはできないと思います。TrafficLight状態が変わるまで、特定の状態のスレッドが他のスレッドを停止しながら通過できるようにするため、これを呼び出しました。

public class TrafficLight<T> {

    private final int maxSequence;
    private final ReentrantLock lock = new ReentrantLock(true);
    private final Condition allClear = lock.newCondition();
    private int registered;
    private int leftInSequence;
    private T openState;

    public TrafficLight(int maxSequence) {
        this.maxSequence = maxSequence;
    }

    public void acquire(T state) throws InterruptedException {
        lock.lock();
        try {
            while ((this.openState != null && !this.openState.equals(state)) || leftInSequence == maxSequence) {
                allClear.await();
            }
            if (this.openState == null) {
                this.openState = state;
            }
            registered++;
            leftInSequence++;
        } finally {
            lock.unlock();
        }
    }

    public void release() {
        lock.lock();
        try {
            registered--;
            if (registered == 0) {
                openState = null;
                leftInSequence = 0;
                allClear.signalAll();
            }
        } finally {
            lock.unlock();
        }
    }
}

acquire()別の状態がアクティブな場合、非アクティブになるまでブロックします。

これmaxSequenceは、スレッドの枯渇を防ぐのに役立ち、最大数のスレッドのみが順番に渡されるようにします (その後、他のスレッドと同様にキューに入れる必要があります)。代わりに時間枠を使用するバリアントを作成できます。

あなたの問題については、開始時someMethod1()と終了someMethod2()時にそれぞれ異なる状態で acquire() を呼び出しますrelease()

于 2013-01-03T17:15:31.520 に答える