Javaで最も頻繁に発生する並行性の問題は何ですか?[閉まっている]


191

これは、Javaにおける一般的な同時実行性の問題に関する一種の投票です。例としては、古典的なデッドロックや競合状態、あるいはSwingのEDTスレッド化バグが考えられます。考えられる問題の範囲だけでなく、最も一般的な問題にも興味があります。したがって、Javaの同時実行性バグの特定の回答をコメントごとに1つ残し、遭遇したものを見つけたら投票してください。


16
なぜこれが閉じているのですか?これは、Javaで並行性を要求する他のプログラマーにとっても、他のJava開発者が最も多くの並行性障害のクラスを観察しているのかを理解するためにも役立ちます。
L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳ 2013年

@Longpokeクローズメッセージは、クローズされた理由を説明します。これは特定の「正解」の質問ではなく、投票/リストの質問です。また、Stack Overflowはこの種の質問をホストするつもりはありません。そのポリシーに同意しない場合は、metaでそれについて話し合うことをお勧めします。
Andrzej Doyle

7
この記事は1日あたり100以上のビューを獲得しているため、コミュニティは同意しないと思います。同時実行の問題contemplateltd.com/threadsafeを修正するために特別に設計された静的分析ツールの開発に携わっているので、私はそれが非常に便利であることがわかりました。よく発生する並行性の問題のバンクがあることは、ThreadSafeのテストと改善に最適です。
クレイグマンソン2014年

Java Concurrencyのコードレビューチェックリストは、この質問への回答で言及されている落とし穴のほとんどを、日常のコードレビューに便利な形式で要約しています。
leventov

回答:


125

私が見た最も一般的な同時実行の問題は、あるスレッドによって書き込まれたフィールドが別のスレッドによって表示れることが保証されいないことを認識していないことです。これの一般的なアプリケーション:

class MyThread extends Thread {
  private boolean stop = false;

  public void run() {
    while(!stop) {
      doSomeWork();
    }
  }

  public void setStop() {
    this.stop = true;
  }
}

停止が揮発性でないかsetStop同期されrunていない限り、これは機能するとは限りません。この間違いは特に悪魔的です。99.999%の場合、リーダースレッドが最終的に変更を確認するため、実際には問題になりません。


9
これに対する優れたソリューションは、ストップインスタンス変数をAtomicBooleanにすることです。JMMの問題からユーザーを保護しながら、不揮発性のすべての問題を解決します。
カークワイリー

39
それは「数分間」よりも悪いです-あなたはそれを見ることは決してないかもしれません。メモリモデルの下では、JVMはwhile(!stop)をwhile(true)に最適化することが許可されており、それからあなたは夢中になります。これは一部のVMでのみ発生する可能性があり、サーバーモードでのみ、JVMがループのx回の反復後に再コンパイルする場合などにのみ発生します。
Cowan、

2
なぜ揮発性ブール値ではなくAtomicBooleanを使用したいのですか?私はバージョン1.4以降用に開発していますが、揮発性と宣言するだけの落とし穴はありますか?
プール

2
ニック、それは原子CASが通常揮発性よりもさらに速いためだと思います。それは、Java 5でありとして強いメモリバリア保証を持っていない1.4に揮発性として同期使用するには1.4あなたの唯一の安全なオプションIMHOのために開発している場合がされて
Kutzi

5
@Thomas:それはJavaメモリモデルが原因です。詳細について知りたい場合は、このドキュメントを読んでください(たとえば、Brian GoetzによるJava Concurrency in Practiceは、それについて説明しています)。つまり、メモリの同期キーワード/構成(揮発性、同期、AtomicXyzなど)を使用しない限り、1つのスレッドは、別のスレッドによって行われたフィールドに加えられた変更を確認できるという保証はありません
Kutzi

178

2つの異なるオープンソースライブラリが次のようなことをしたときに、私の最大の同時実行性の問題の1番目が発生しました。

private static final String LOCK = "LOCK";  // use matching strings 
                                            // in two different libraries

public doSomestuff() {
   synchronized(LOCK) {
       this.work();
   }
}

一見すると、これはかなり簡単な同期の例のように見えます。しかしながら; 文字列はJavaでインターンされるため、リテラル文字列"LOCK"は同じインスタンスであるjava.lang.Stringことがわかります(たとえそれらが互いに完全に異なるように宣言されていても)。結果は明らかに悪いです。


62
これが私がプライベートなstatic final Object LOCK = new Object();を好む理由の1つです。
アンジェイドイル

17
私はそれを愛する-ああ、これは厄介です:)
するThorbjörnRavnアンデルセン

7
これは、Javaのpuzzlers 2の良いものだ
ドブワッサーマン

12
実際に...それは本当に私にあなたが文字列で同期することを許可することをコンパイラに拒否させたいと思っています。文字列のインターンを考えると、それが「良いこと(tm)」になるケースはありません。
Jared

3
@Jared: "文字列が抑留されるまで"は意味がありません。文字列は魔法のように「抑留」されません。String.intern()は、指定されたStringの正規のインスタンスが既にない限り、別のオブジェクトを返します。また、すべてのリテラル文字列と文字列値定数式がインターンされます。常に。JLSのString.intern()および§3.10.5のドキュメントを参照してください。
ローレンスゴンサルベス

65

古典的な問題の1つは、同期しているオブジェクトを同期中に変更することです。

synchronized(foo) {
  foo = ...
}

その後、他の並行スレッドが別のオブジェクトで同期しており、このブロックは期待する相互排除を提供しません。


19
「有効なセマンティクスを持つ可能性が低い、非最終フィールドでの同期」と呼ばれる、これに対するIDEAインスペクションがあります。非常に素晴らしい。
ジェンS.

8
は...今、それは拷問された説明です。「有用なセマンティクスを持つ可能性は低い」は「壊れている可能性が最も高い」と表現するのがよいでしょう。:)
Alex Miller

ReadWriteLockにこれがあったのはBitter Javaだったと思います。幸い、今ではjava.util.concurrency.locksが用意されており、Dougの方がややこしいです。
トム・ホーティン-09年

私もこの問題を頻繁に見ました。さらに言えば、最終オブジェクトでのみ同期します。FindBugs et al。はい、そうです。
gimpf 2009年

これは割り当て中の問題だけですか?(以下のマップを使用した@Alex Millerの例を参照)そのマップの例にも同じ問題がありますか?
Alex Beardsley、

50

一般的な問題は、同期せずに複数のスレッドから(多くの場合、静的変数にキャッシュすることにより)CalendarやSimpleDateFormatなどのクラスを使用することです。これらのクラスはスレッドセーフではないため、マルチスレッドアクセスは最終的に不整合な状態で奇妙な問題を引き起こします。


このバグを含むいくつかのバージョンのオープンソースプロジェクトを知っていますか?現実のソフトウェアにおけるこのバグの具体的な例を探しています。
リプログラマ

47

ダブルチェックされたロック。概して。

私がBEAで働いていたときに問題を学び始めたパラダイムは、人々がシングルトンを次のようにチェックするというものです。

public Class MySingleton {
  private static MySingleton s_instance;
  public static MySingleton getInstance() {
    if(s_instance == null) {
      synchronized(MySingleton.class) { s_instance = new MySingleton(); }
    }
    return s_instance;
  }
}

別のスレッドが同期ブロックに入り、s_instanceがnullでなくなるため、これは機能しません。したがって、自然な変化はそれを作ることです:

  public static MySingleton getInstance() {
    if(s_instance == null) {
      synchronized(MySingleton.class) {
        if(s_instance == null) s_instance = new MySingleton();
      }
    }
    return s_instance;
  }

Javaメモリモデルがサポートしていないため、これも機能しません。これを機能させるには、s_instanceをvolatileとして宣言する必要があり、それでもJava 5でのみ機能します。

Javaメモリモデルの複雑さに精通していない人は、これを常に混乱させます


7
enumシングルトンパターンは、これらすべての問題を解決します(これに関するJosh Blochのコメントを参照)。その存在についての知識は、Javaプログラマーの間でより広く普及するはずです。
Robin

私はまだ、シングルトンの遅延初期化が実際に適切であった単一のケースに遭遇する必要があります。そうであれば、同期するメソッドを宣言するだけです。
Dov Wasserman、

3
これは、シングルトンクラスの遅延初期化に使用するものです。また、Javaによって暗黙的に保証されているため、同期は不要です。クラスFoo {静的クラスホルダー{静的Foo foo = new Foo(); } static Foo getInstance(){return Holder.foo; }}
Irfan Zulfiqar

私が覚えていることから、それはピューの方法と呼ばれるイルファン
クリスR

@Robin、静的初期化子を使用する方が簡単ではないですか?これらは常に同期して実行されることが保証されています。
matt b

47

特に反復または複数の操作中に、によって返されたオブジェクトを適切に同期しないCollections.synchronizedXXX()

Map<String, String> map = Collections.synchronizedMap(new HashMap<String, String>());

...

if(!map.containsKey("foo"))
    map.put("foo", "bar");

それは間違っています。単一の操作であるにもかかわらずsynchronized、呼び出しcontainsとの間のマップの状態はput別のスレッドによって変更できます。そのはず:

synchronized(map) {
    if(!map.containsKey("foo"))
        map.put("foo", "bar");
}

またはConcurrentMap実装あり:

map.putIfAbsent("foo", "bar");

5
または、ConcurrentHashMapとputIfAbsentを使用してください。
トム・ホーティン-09年

37

おそらく正確にあなたが求めているものではないかもしれませんが、私が遭遇した最も頻繁な同時実行関連の問題は(おそらくそれが通常のシングルスレッドコードで発生するため)

java.util.ConcurrentModificationException

次のようなことが原因で発生します:

List<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c"));
for (String string : list) { list.remove(string); }

いいえ、それは完全に私が探しているものです。ありがとう!
Alex Miller

30

同期されたコレクションは、実際よりも多くの保護を提供し、呼び出し間でロックを保持することを忘れると考えるのは簡単です。私はこの間違いを数回見ました:

 List<String> l = Collections.synchronizedList(new ArrayList<String>());
 String[] s = l.toArray(new String[l.size()]);

たとえば、上記の2行目ではtoArray()size()メソッドとメソッドはどちらもそれ自体でスレッドセーフですが、はとsize()は別に評価さtoArray()れ、リストのロックはこれら2つの呼び出し間で保持されません。

リストからアイテムを同時に削除する別のスレッドでこのコードを実行すると、遅かれ早かれString[]、リスト内のすべての要素を保持するために必要なサイズよりも大きく、末尾にnull値を持つ新しい戻り値が返されます。Listへの2つのメソッド呼び出しが1行のコードで発生するため、これはどういうわけかアトミック操作であると考えられがちですが、そうではありません。


5
良い例え。「アトミック操作の構成はアトミックではない」ので、私はもっと一般的にこれをチョークすると思います。(別の簡単な例については、volatile field ++を参照してください)
Alex Miller

29

私が作業しているときに見られる最も一般的なバグは、プログラマーがEDTでサーバー呼び出しなどの長い操作を実行し、GUIを数秒間ロックし、アプリを応答不能にすることです。


それらの答えの1つは、1つ以上のポイントを与えることができればいいのですが
Epaga

2
EDT =イベントディスパッチスレッド
mjj1409

28

ループでwait()(またはCondition.await())を忘れて、待機条件が実際に真であることを確認します。これがないと、偽のwait()ウェイクアップからバグが発生します。正規の使用法は次のとおりです。

 synchronized (obj) {
     while (<condition does not hold>) {
         obj.wait();
     }
     // do stuff based on condition being true
 }

26

別の一般的なバグは、貧弱な例外処理です。バックグラウンドスレッドが例外をスローするときに、それを適切に処理しないと、スタックトレースがまったく表示されない場合があります。または、バックグラウンドタスクが実行を停止し、例外の処理に失敗したために再び開始しない場合もあります。


ええ、ハンドラーでこれを処理するための優れたツールがあります。
Alex Miller

3
これをより詳細に説明する記事または参照へのリンクを投稿できますか?
Abhijeet Kashnia 2010年

22

Brian Goetzでクラスを受講するまでは、同期getterによって変更されたプライベートフィールドの非同期setterが更新された値を返すことが保証されていないことに気づきませんでした。変数が読み取りと書き込みの両方で同期ブロックによって保護されている場合のみ、変数の最新の値が保証されます。

public class SomeClass{
    private Integer thing = 1;

    public synchronized void setThing(Integer thing)
        this.thing = thing;
    }

    /**
     * This may return 1 forever and ever no matter what is set
     * because the read is not synched
     */
    public Integer getThing(){
        return thing;  
    }
}

5
後のJVM(1.5以降)では、volatileを使用することによっても修正されます。
James Schek、2009年

2
必ずしも。volatileは最新の値を提供するため、1が永久に戻るのを防ぎますが、ロックは提供しません。近いですが、まったく同じではありません。
ジョンラッセル

1
@JohnRussell私は揮発性が前に起こる関係を保証すると思った。それは「ロック」ではありませんか?「揮発性変数への書き込み(8.3.1.4)v任意のスレッドによるvの後続のすべての読み取りと同期します(後続は同期の順序に従って定義されます)。」
Shawn

15

シングルスレッドのコードを書いていると思いますが、変更可能な静的(シングルトンを含む)を使用しています。明らかに、それらはスレッド間で共有されます。これは驚くほど頻繁に発生します。


3
はい、そうです!ミュータブルスタティックはスレッドの制限を解除します。驚いたことに、JCiPでもCPJでも、この落とし穴について何も見つかりませんでした。
ジュリアンチャスタン2009年

これが並行プログラミングをしている人々にとって明白であることを願っています。スレッドの安全性を確認する最初の場所は、グローバル状態です。
2011年

1
@Gary Thingは、並行プログラミングをしているとは考えていません。
トム・ホーティン-2011年

15

同期されたブロック内から任意のメソッド呼び出しを行うことはできません。

デイブ・レイは彼の最初の答えでこれに触れました、そして実際に私はまた、同期化されたメソッド内からリスナーのメソッドを呼び出すことと関係があるデッドロックに遭遇しました。より一般的な教訓は、同期されたブロック内からメソッド呼び出しを「実際に」行うべきではないということです-呼び出しが長時間実行されるか、デッドロックになるか、または何でもわかりません。

この場合、通常は一般的に、解決策は同期ブロックのスコープを減らして、コードの重要なプライベートセクションのみを保護することでした。

また、同期ブロックの外側でリスナーのコレクションにアクセスしていたため、コピーオンライトコレクションに変更しました。または、単にコレクションの防御的なコピーを作成することもできます。つまり、通常、未知のオブジェクトのコレクションに安全にアクセスするための代替手段があります。


13

私が遭遇した最新の同時実行関連のバグは、コンストラクターでExecutorServiceを作成したオブジェクトでしたが、オブジェクトが参照されなくなったときに、ExecutorServiceをシャットダウンしていませんでした。したがって、数週間にわたって数千のスレッドがリークし、最終的にシステムがクラッシュしました。(技術的には、クラッシュはしませんでしたが、実行を続けている間は正常に機能しなくなりました。)

技術的には、これは同時実行の問題ではないと思いますが、java.util.concurrencyライブラリの使用に関連する問題です。


11

特にマップに対する不均衡な同期は、かなり一般的な問題のようです。多くの人々は、マップ(ConcurrentMapではなく、HashMapと言います)へのputの同期と、getの同期を行わないことで十分であると考えています。ただし、これにより、再ハッシュ中に無限ループが発生する可能性があります。

ただし、読み取りと書き込みで状態を共有しているすべての場所で同じ問題(部分同期)が発生する可能性があります。


11

リクエストごとに設定される可変フィールドがある場合、サーブレットで同時実行の問題が発生しました。ただし、すべてのリクエストに対してサーブレットインスタンスは1つしかないため、これはシングルユーザー環境で完全に機能しましたが、複数のユーザーがサーブレットをリクエストすると、予期しない結果が発生しました。

public class MyServlet implements Servlet{
    private Object something;

    public void service(ServletRequest request, ServletResponse response)
        throws ServletException, IOException{
        this.something = request.getAttribute("something");
        doSomething();
    }

    private void doSomething(){
        this.something ...
    }
}

10

バグではありませんが、最悪の罪は、他のユーザーが使用するライブラリを提供することですが、どのクラス/メソッドがスレッドセーフであり、どのクラス/メソッドが単一スレッドからのみ呼び出されなければならないかなどを述べていません。

Goetzの本に記載されている同時実行アノテーション(@ ThreadSafe、@ GuardedByなど)をより多くの人が利用する必要があります。


9

私の最大の問題は常にデッドロックであり、特にロックを保持したままリスナーが起動されることが原因でした。これらの場合、2つのスレッド間で逆ロックを取得するのは非常に簡単です。私の場合、1つのスレッドで実行されているシミュレーションと、UIスレッドで実行されているシミュレーションの視覚化との間。

編集:2番目の部分を別の答えに移動しました。


最後の1つを別の答えに分割できますか?投稿ごとに1つ保持しましょう。これらは2つの本当に良いものです。
Alex Miller

9

クラスのコンストラクター内でスレッドを開始することには問題があります。クラスが拡張されている場合、サブクラスのコンストラクターが実行される前にスレッドを開始できます。


8

共有データ構造の可変クラス

Thread1:
    Person p = new Person("John");
    sharedMap.put("Key", p);
    assert(p.getName().equals("John");  // sometimes passes, sometimes fails

Thread2:
    Person p = sharedMap.get("Key");
    p.setName("Alfonso");

これが発生すると、コードはこの簡略化された例よりもはるかに複雑になります。バグの複製、発見、修正は困難です。おそらく、特定のクラスを不変としてマークし、特定のデータ構造を不変オブジェクトのみを保持するものとしてマークできれば、回避することができます。


8

文字列リテラルがインターンされ、同じ文字列リテラルを使用してJVM内の他のユーザーと共有されるため、文字列リテラルまたは文字列リテラルによって定義された定数での同期は(潜在的に)問題です。この問題は、アプリケーションサーバーやその他の「コンテナ」シナリオで発生していることを知っています。

例:

private static final String SOMETHING = "foo";

synchronized(SOMETHING) {
   //
}

この場合、ロックに文字列「foo」を使用している誰もが同じロックを共有しています。


ロックされている可能性があります。問題は、文字列がインターンされているWHENのセマンティクスが定義されていない(または、IMNSHOで定義されていない)ことです。コンパイラー時定数「foo」がインターンされます。ネットワークインターフェースから入ってくる「foo」は、そうする場合にのみインターンされます。
カークワイリー

そうです、私がインターンされることが保証されているリテラル文字列定数を具体的に使用したのはそのためです。
Alex Miller

8

将来的には、Javaの主な問題は、コンストラクターの可視性保証(の欠如)になると思います。たとえば、次のクラスを作成した場合

class MyClass {
    public int a = 1;
}

次に、MyClassのプロパティaを別のスレッドから読み取るだけです。JavaVMの実装とムードに応じて、MyClass.aは0または1になります。今日、「a」が1になる可能性は非常に高いです。しかし、将来のNUMAマシンでは、これは異なる場合があります。多くの人々はこれに気づいておらず、初期化段階でマルチスレッドを気にする必要がないと信じています。


私はこれを少し意外と思いますが、あなたが賢い男のティムであることを知っているので、参照なしで取り上げます。:)しかし、aがfinalだった場合、これは問題にはなりませんよね?次に、構築中に最終的な凍結セマンティクスに拘束されるでしょうか?
Alex Miller

私はまだJMMで私を驚かせるものを見つけているので、私は私を信用しませんが、これについてはかなり確信しています。cs.umd.edu/~pugh/java/memoryModel/…も参照してください。フィールドがfinalである場合、それは問題ではありません。初期化フェーズの後で表示されます。
Tim Jansen、

2
新たに作成されたインスタンスの参照が、コンストラクターが戻る/終了する前にすでに使用されている場合、これは問題のみです。たとえば、クラスは構築中にパブリックプールに自身を登録し、他のスレッドがそれへのアクセスを開始します。
ReneS 2009年

3
MyClass.aは静的アクセスを示し、 'a'はMyClassの静的メンバーではありません。それ以外は、「ReneS」状態であるため、これは、たとえばコンストラクタの外部マップに「this」を追加するなど、未完成のオブジェクトへの参照がリークされた場合にのみ問題になります。
Markus Jevring、2011年

7

オブジェクトでnotify()またはwait()を呼び出す前に同期を忘れることは、私がよくする最もおかしな間違いです。


8
ほとんどの同時実行問題とは異なり、これは簡単に見つけられませんか?少なくともここでIllegalMonitorStateExceptionが発生します...
Outlaw Programmer

ありがたいことにそれを見つけるのは非常に簡単です...しかし、それはまだ私の時間を無駄にするよりも愚かな間違いです:)
Dave Ray

7

ローカルの「new Object()」をミューテックスとして使用します。

synchronized (new Object())
{
    System.out.println("sdfs");
}

これは役に立たない。


2
これはおそらく役に立たないかもしれませんが、まったく同期することはいくつかの興味深いことをします...確かに毎回新しいオブジェクトを作成することは完全に無駄です。
TREE

4
それは無駄ではありません。ロックのないメモリの壁です。
David Roussel、

1
@デビッド:唯一の問題- JVMがすべてで、このようなロックを除去することによって、それを最適化することができ
yetanothercoder

@insighter私はあなたの意見が共有されていると思いますibm.com/developerworks/java/library/j-jtp10185/index.html私はそれが愚かなことであることに同意します、あなたの追悼の障壁がいつ同期するかわからないので、私はそれが何もしていないことを指摘しているだけでした
David Roussel

7

もう1つの一般的な「同時実行性」の問題は、まったく必要ない場合に同期コードを使用することです。たとえば、プログラマーは(StringBufferまたはjava.util.Vectorメソッドのローカル変数として)を使用しているのを見ています。


1
これは問題ではありませんが不要です。JVMがグローバルメモリに対してデータを同期するように指示し、マルチCPUでうまく実行できない可能性があるため、同期ブロックを同時に使用する人はいません。
ReneS 2009年

6

ロック保護されているが、通常は連続してアクセスされる複数のオブジェクト。ロックが異なるコードで異なる順序で取得され、デッドロックが発生する場合がいくつかあります。


5

this内部クラスのがthis外部クラスのでないことを認識していません。通常、を実装する匿名の内部クラスですRunnable。根本的な問題は、同期はすべてObjectのの一部であるため、静的な型チェックが事実上存在しないことです。これはusenetで少なくとも2回見たことがあります。また、Brian Goetz'z Java Concurrency in Practiceにも表示されます。

BGGAクロージャーはthis、クロージャーが存在しないため(this外部クラスを参照)、この問題の影響を受けません。非thisオブジェクトをロックとして使用すると、この問題などが回避されます。


3

ロック用の静的変数などのグローバルオブジェクトの使用。

これは、競合のために非常に悪いパフォーマンスにつながります。


まあ、時には、そうでない場合もあります。簡単なことなら
gimpf 2009年

スレッド化は、与えられた問題のパフォーマンスを向上させるのに役立つと仮定すると、複数のスレッドがロックによって保護されているコードにアクセスするとすぐに、常にパフォーマンスが低下します。
kohlerm 2009年

3

正直?が登場する前、java.util.concurrent私が日常的に遭遇した最も一般的な問題は、「スレッドスラッシング」と呼ばれるものでした。並行性のためにスレッドを使用しているが、生成するスレッドが多すぎて、スラッシングが発生するアプリケーションです。


java.util.concurrentが利用できるようになったため、さらに多くの問題が発生することを意味していますか?
アンジェイドイル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.