5

次のクラスを実行すると、ExecutionService はしばしばデッドロックします。

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class ExecutorTest {
public static void main(final String[] args) throws InterruptedException {
    final ExecutorService executor = Executors.newFixedThreadPool(10);

    final HashMap<Object, Object> map = new HashMap<Object, Object>();
    final Collection<Callable<Object>> actions = new ArrayList<Callable<Object>>();
    int i = 0;
    while (i++ < 1000) {
        final Object o = new Object();
        actions.add(new Callable<Object>() {
            public Object call() throws Exception {
                map.put(o, o);
                return null;
            }
        });
        actions.add(new Callable<Object>() {
            public Object call() throws Exception {
                map.put(new Object(), o);
                return null;
            }
        });
        actions.add(new Callable<Object>() {
            public Object call() throws Exception {
                for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
                    iterator.next();
                }
                return null;
            }
        });
    }
    executor.invokeAll(actions);
    System.exit(0);
}

}

では、なぜこれが起こるのですか?またはさらに良いことに、カスタム抽象マップの実装がスレッドセーフであることを確認するテストを作成するにはどうすればよいでしょうか? (一部の実装には複数のマップがあり、別のデリゲートはキャッシュ実装など)

背景: これは Windows 上の Java 1.6.0_04 および 1.6.0_07 で発生します。問題が sun.misc.Unsafe.park() にあることはわかっています。

  • Core2 Duo 2.4Ghz ラップトップで問題を再現できますが、デバッグ中には再現できません
  • 作業中の Core2 Quad でデバッグできますが、RDP でハングアップしているため、明日までスタック トレースを取得できません。

以下のほとんどの回答は、HashMap のスレッド以外の安全性に関するものですが、HashMap にロックされたスレッドは見つかりませんでした。それはすべて ExecutionService コード (および Unsafe.park()) にありました。明日、スレッドを詳しく調べます。

これはすべて、カスタムの抽象 Map 実装がスレッドセーフではなかったためです。そのため、すべての実装がスレッドセーフであることを確認することにしました。本質的に、ConcurrentHashMap などの理解がまさに期待どおりであることを確認したいのですが、ExecutionService が奇妙に欠けていることがわかりました...

4

4 に答える 4

16

よく知られているスレッドセーフではないクラスを使用していて、デッドロックについて不平を言っています。ここで何が問題なのかわかりません。

また、いかがですかExecutionService

strangely lacking

?

eg aHashMapを使用しても、せいぜい古いデータしか得られないというのはよくある誤解です。それだけで JVM を爆破できる美しい競合状態をご覧ください。

なぜこれが起こるのかを理解することは非常に難しいプロセスであり、JVM とクラス ライブラリの両方の内部についての知識が必要です。

ConcurrentHashMap については、javadocを読んでください。質問が明確になるはずです。そうでない場合は、Java Concurrency in Practice をご覧ください。


アップデート:

状況を再現できましたが、デッドロックではありません。actions完了しない実行の 1 つ。スタック トレースは次のとおりです。

"pool-1-thread-3" prio=10 tid=0x08110000 nid=0x22f8 runnable [0x805b0000]
java.lang.Thread.State: RUNNABLE
at ExecutorTest$3.call(ExecutorTest.java:36)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
at java.util.concurrent.FutureTask.run(FutureTask.java:138)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:619)

私がリンクした正確なケースのように見えます-HashMapのサイズが変更され、サイズ変更の内部メカニズムにより、イテレーターが無限ループに陥ります。

これが発生すると、invokeAll二度と戻らず、プログラムがハングします。しかし、これはデッドロックでもライブロックでもなく、競合状態です。

于 2009-07-01T09:33:56.200 に答える
2

デッドロックで何を理解していますか?

コードには少なくとも 2 つの問題があります。はHashMap複数のスレッドから同時に使用されるため、無限ループに入る可能性があります。基礎となるデータ構造を潜在的に変更しながら、エントリセットを反復しています(個々の操作が同期されていた場合でも、hasNextアトミックnextではない場合でも)。

また、最新の Synhronized Security Release (SSR) で最新の 1.6.0 のバージョンが 1.6.0_13 および 1.6.0_14 であることにも注意してください。

于 2009-07-01T09:34:59.630 に答える
1

あなたの地図は同時に修正されていると思います。反復アクションの進行中に put() が呼び出されると、特定の条件下 (特にサイズ変更が発生した場合) で、無限ループに陥る可能性があります。これはよく知られた動作です (こちらを参照)。

デッドロックと無限ループは、非常に異なった形で現れます。真のデッドロックがある場合、スレッド ダンプはインターロックしているスレッドを明確に示します。一方、無限ループに入ると、CPU の使用率が急上昇し、ダンプを取得するたびにスタック トレースが変化します。

これは Executor とは何の関係もなく、そのように使用するように設計されていないHashMap の安全でない同時使用とすべて関係があります。実際、少数のスレッドの配列でこの問題を再現するのは非常に簡単です。

これに対する最善の解決策は、ConcurrentHashMap に切り替えることです。同期された HashMap または Hashtable に切り替えると、無限ループにはなりませんが、反復中に ConcurrentModificationExceptions が発生する可能性があります。

于 2009-07-17T06:13:29.770 に答える
0

テストを機能させるという点では、次の代わりに:

 executor.invokeAll(actions);

使用する

 executor.invokeAll(actions, 2, TimeUnit.SECONDS);

また、テストを実際に機能させる (そしてエラーを報告する) には、次のようなことを行う必要があることに注意してください。

 List<Future> results = executor.invokeAll(actions, 2, TimeUnit.SECONDS);
 executor.shutdown();
 for (Future result : results) {
     result.get(); // This will report the exceptions encountered when executing the action ... the ConcurrentModificationException I wanted in this case (or CancellationException in the case of a time out)
 }
 //If we get here, the test is successful... 
于 2009-07-01T10:35:32.270 に答える