0

同じことを実行しているスレッドを一時停止して、そのうちの1つが終了するのを待つことにより、並行プログラムを改善しています。ただし、スレッドを適切に起こすことはできません。これがコードです。

//to store graphs, if a thread finds the graph it is going to compute is in the entry, it waits, otherwise it compute then notify all other threads waiting on it.
Map<Graph, Object> entry = new ConcurrentHashMap<Graph, Object>();

public Result recursiveMethod(Graph g) {
        if (entry.get(g) != null) {//if the graph is in the entry, waits
            synchronized(entry.get(g)) {
                entry.get(g).wait();
            }
            //wakes up, and directly return the result
            return result;
        }
        synchronized(entry) {
            if (entry.get(g) == null)//if the graph is not in the entry, continue to compute
            entry.put(g,new Object());
        }
        //compute the graph recursively calls this method itself...
        calculate here...
        //wake up threads waiting on it, and remove the graph from entry
        synchronized(entry.get(g)){
            entry.get(g).notifyAll();
        }
        entry.remove(g);
        return result;
}

このメソッドは、多数のスレッドによって呼び出されます。スレッドは、計算を開始する前に、エントリを調べて、同じグラフを計算している別のスレッドがあるかどうかを確認します。もしそうなら、それは待っています。そうでない場合は、計算を続行します。結果を把握した後、待機しているすべてのスレッドに通知します。

マップを使用して、グラフとオブジェクトをペアにします。オブジェクトはロックです。このマップは 2 つの同一のグラフを認識できることに注意してください。つまり、次のコードは true を返します。

Graph g = new Graph();
entry.put(g, new Object());
Graph copy = new Graph(g);
entry.get(g) == entry.get(copy) //this is true

したがって、entry.get(g) はロック/モニターとして問題ないはずです。ただし、ほとんどのスレッドは起動されておらず、3 ~ 4 個のスレッドのみが起動されています。待機しているスレッドの数がコンピューターが作成できるスレッドの数と等しい場合、つまりすべてのスレッドが待機している場合、このプログラムは決して終了しません。

entry.get(g).notifyAll() が機能しないのはなぜですか?

4

2 に答える 2

1

マップをチェックする時間とマップ上で操作する時間との間に同期されていないギャップがあるという事実により、スレッドが正しく進行しない可能性がある多くの穴がロジックに存在します。マップ チェックの外で同期するか、ConcurrentMaps の特別なアトミック メソッドを使用する必要があります。

並行コードを作成するとき、バックグラウンドで悪意のある gnome が実行され、可能な限り変更を加えているふりをするのが好きです (同期ブロックの外など)。開始するための最初の例を次に示します。

    if (entry.get(g) != null) {//if the graph is in the entry, waits
        synchronized(entry.get(g)) {

entry.get()同期ブロックの外で 2 回呼び出します。したがって、取得する値はこれら 2 つの呼び出しで異なる可能性があります (邪悪な gnome は可能な限り頻繁にマップを変更します)。実際、同期しようとすると null になる可能性があり、例外がスローされます。

さらに、wait()ループ条件が変化するのを待っている間は、常にループ内で呼び出しを行う必要があります(偽のウェイクアップの可能性、またはあなたの場合は複数のウェイクアップの可能性があるため)。最後に、通知するにループ条件を変更する必要があります。@AdrianShumは、待機/通知を正しく使用する方法のかなり良い概要を提供しました。あなたのwhileループはすべての周りにあるべきではありませんが、wait呼び出しだけの周りの同期ブロック内にあるべきです。これは、InterruptedException (別の問題) に対処するためではなく、偽のウェイクアップとnotifyAll呼び出しに対処するためです。notifyAll 待機中のすべてのスレッドを呼び出すと、ウェイクアップしますが、続行できるのは1つだけなので、残りは待機に戻る必要があります(したがって、whileループ)。

要するに、並行コードを書くのは難しく、実装しようとしているものは単純ではありません。このコードを完成させる前に、まず良い本 (Josh Bloch の「Java Concurrency In Practice」など) を読むことをお勧めします。

于 2012-07-27T03:46:12.393 に答える
0

実際、@jtahlborn はすでに問題の鍵を上げています。ここで最も明白な問題は何かを説明することで補足しようとしています。

Condition の基本と、それらの競合状態を通常のシグナリングとして解決できる理由 (Windows など) を理解するようにしてください。

あなたのロジックは次のようになりました(obj同じオブジェクトを参照していると仮定します):

スレッド 1:

if (!hasResult) {
    synchronized(obj) {
        obj.wait();
    }
}

スレッド 2:

hasResult = false;
// do your work
synchronized(obj) {
   obj.notify();
}
hasResult= true;

スレッド1とスレッド2が並行して実行されることを知っておく必要があるため、次のようなものがあるかもしれません

Thread 1                   Thread 2
                         hasResult = false
if (!hasResult)
                         do your work
                         synchronized(obj)
                         obj.notify()
                         end synchronized(obj)

synchronized(obj)
obj.wait()
end synchronized(obj)

スレッド 1 は永遠に待機します。

あなたがすべきことは

スレッド 1:

synchronized(obj) {
    while (hasResult) {
        obj.wait();
    }
}

スレッド 2:

hasResult = false;
synchronized(obj) {
   // do your work
   obj.notify();
   hasResult=true;
}

それは@jtahlbornが話している最大の穴の1つです(他にもあります)。同期ブロックでは、設定条件とチェック条件がすべて保護されていることに注意してください。これが、Condition 変数が前に示した競合状態を解決する方法の主な基本的な考え方です。最初にアイデアを理解してから、コードをより合理的なものに再設計してください。

于 2012-07-27T07:17:37.710 に答える